refactor(engine): 전투 및 진행 로직 개선
- combat_tick_service: 전투 틱 처리 로직 확장 - progress_service: 진행 상태 처리 개선 - skill_service: 스킬 시스템 업데이트 - potion_service: 포션 처리 로직 수정
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
|
import 'package:asciineverdie/data/class_data.dart';
|
||||||
import 'package:asciineverdie/data/skill_data.dart';
|
import 'package:asciineverdie/data/skill_data.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
|
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/class_traits.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/potion_service.dart';
|
import 'package:asciineverdie/src/core/engine/potion_service.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/skill_service.dart';
|
import 'package:asciineverdie/src/core/engine/skill_service.dart';
|
||||||
import 'package:asciineverdie/src/core/model/combat_event.dart';
|
import 'package:asciineverdie/src/core/model/combat_event.dart';
|
||||||
@@ -94,6 +96,17 @@ class CombatTickService {
|
|||||||
totalDamageDealt = dotResult.totalDamageDealt;
|
totalDamageDealt = dotResult.totalDamageDealt;
|
||||||
newEvents.addAll(dotResult.events);
|
newEvents.addAll(dotResult.events);
|
||||||
|
|
||||||
|
// 클래스 패시브 조회 (healingBonus, firstStrikeBonus, multiAttack)
|
||||||
|
final klass = ClassData.findById(state.traits.classId);
|
||||||
|
final healingBonus =
|
||||||
|
klass?.getPassiveValue(ClassPassiveType.healingBonus) ?? 0.0;
|
||||||
|
final healingMultiplier = 1.0 + healingBonus;
|
||||||
|
final firstStrikeBonus =
|
||||||
|
klass?.getPassiveValue(ClassPassiveType.firstStrikeBonus) ?? 0.0;
|
||||||
|
final hasMultiAttack =
|
||||||
|
klass?.hasPassive(ClassPassiveType.multiAttack) ?? false;
|
||||||
|
var isFirstPlayerAttack = combat.isFirstPlayerAttack;
|
||||||
|
|
||||||
// 긴급 물약 자동 사용 (HP < 30% 또는 MP < 50%)
|
// 긴급 물약 자동 사용 (HP < 30% 또는 MP < 50%)
|
||||||
final potionResult = _tryEmergencyPotion(
|
final potionResult = _tryEmergencyPotion(
|
||||||
playerStats: playerStats,
|
playerStats: playerStats,
|
||||||
@@ -102,6 +115,7 @@ class CombatTickService {
|
|||||||
playerLevel: state.traits.level,
|
playerLevel: state.traits.level,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
potionService: potionService,
|
potionService: potionService,
|
||||||
|
healingMultiplier: healingMultiplier,
|
||||||
);
|
);
|
||||||
if (potionResult != null) {
|
if (potionResult != null) {
|
||||||
playerStats = potionResult.playerStats;
|
playerStats = potionResult.playerStats;
|
||||||
@@ -123,6 +137,10 @@ class CombatTickService {
|
|||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
calculator: calculator,
|
calculator: calculator,
|
||||||
skillService: skillService,
|
skillService: skillService,
|
||||||
|
isFirstPlayerAttack: isFirstPlayerAttack,
|
||||||
|
firstStrikeBonus: firstStrikeBonus > 0 ? firstStrikeBonus : 1.0,
|
||||||
|
hasMultiAttack: hasMultiAttack,
|
||||||
|
healingMultiplier: healingMultiplier,
|
||||||
);
|
);
|
||||||
|
|
||||||
playerStats = attackResult.playerStats;
|
playerStats = attackResult.playerStats;
|
||||||
@@ -132,6 +150,7 @@ class CombatTickService {
|
|||||||
activeDebuffs = attackResult.activeDebuffs;
|
activeDebuffs = attackResult.activeDebuffs;
|
||||||
totalDamageDealt = attackResult.totalDamageDealt;
|
totalDamageDealt = attackResult.totalDamageDealt;
|
||||||
newEvents.addAll(attackResult.events);
|
newEvents.addAll(attackResult.events);
|
||||||
|
isFirstPlayerAttack = attackResult.isFirstPlayerAttack;
|
||||||
|
|
||||||
playerAccumulator -= playerStats.attackDelayMs;
|
playerAccumulator -= playerStats.attackDelayMs;
|
||||||
turnsElapsed++;
|
turnsElapsed++;
|
||||||
@@ -178,6 +197,7 @@ class CombatTickService {
|
|||||||
activeDoTs: activeDoTs,
|
activeDoTs: activeDoTs,
|
||||||
lastPotionUsedMs: lastPotionUsedMs,
|
lastPotionUsedMs: lastPotionUsedMs,
|
||||||
activeDebuffs: activeDebuffs,
|
activeDebuffs: activeDebuffs,
|
||||||
|
isFirstPlayerAttack: isFirstPlayerAttack,
|
||||||
),
|
),
|
||||||
skillSystem: updatedSkillSystem,
|
skillSystem: updatedSkillSystem,
|
||||||
potionInventory: updatedPotionInventory,
|
potionInventory: updatedPotionInventory,
|
||||||
@@ -259,6 +279,7 @@ class CombatTickService {
|
|||||||
required int playerLevel,
|
required int playerLevel,
|
||||||
required int timestamp,
|
required int timestamp,
|
||||||
required PotionService potionService,
|
required PotionService potionService,
|
||||||
|
double healingMultiplier = 1.0,
|
||||||
}) {
|
}) {
|
||||||
// 글로벌 쿨타임 체크
|
// 글로벌 쿨타임 체크
|
||||||
if (timestamp - lastPotionUsedMs < PotionService.globalPotionCooldownMs) {
|
if (timestamp - lastPotionUsedMs < PotionService.globalPotionCooldownMs) {
|
||||||
@@ -281,6 +302,7 @@ class CombatTickService {
|
|||||||
maxHp: playerStats.hpMax,
|
maxHp: playerStats.hpMax,
|
||||||
currentMp: playerStats.mpCurrent,
|
currentMp: playerStats.mpCurrent,
|
||||||
maxMp: playerStats.mpMax,
|
maxMp: playerStats.mpMax,
|
||||||
|
healingMultiplier: healingMultiplier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -316,6 +338,7 @@ class CombatTickService {
|
|||||||
maxHp: playerStats.hpMax,
|
maxHp: playerStats.hpMax,
|
||||||
currentMp: playerStats.mpCurrent,
|
currentMp: playerStats.mpCurrent,
|
||||||
maxMp: playerStats.mpMax,
|
maxMp: playerStats.mpMax,
|
||||||
|
healingMultiplier: healingMultiplier,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -347,6 +370,7 @@ class CombatTickService {
|
|||||||
List<ActiveBuff> activeDebuffs,
|
List<ActiveBuff> activeDebuffs,
|
||||||
int totalDamageDealt,
|
int totalDamageDealt,
|
||||||
List<CombatEvent> events,
|
List<CombatEvent> events,
|
||||||
|
bool isFirstPlayerAttack,
|
||||||
}) _processPlayerAttack({
|
}) _processPlayerAttack({
|
||||||
required GameState state,
|
required GameState state,
|
||||||
required CombatStats playerStats,
|
required CombatStats playerStats,
|
||||||
@@ -358,6 +382,10 @@ class CombatTickService {
|
|||||||
required int timestamp,
|
required int timestamp,
|
||||||
required CombatCalculator calculator,
|
required CombatCalculator calculator,
|
||||||
required SkillService skillService,
|
required SkillService skillService,
|
||||||
|
required bool isFirstPlayerAttack,
|
||||||
|
required double firstStrikeBonus,
|
||||||
|
required bool hasMultiAttack,
|
||||||
|
double healingMultiplier = 1.0,
|
||||||
}) {
|
}) {
|
||||||
final events = <CombatEvent>[];
|
final events = <CombatEvent>[];
|
||||||
var newPlayerStats = playerStats;
|
var newPlayerStats = playerStats;
|
||||||
@@ -442,6 +470,7 @@ class CombatTickService {
|
|||||||
skill: selectedSkill,
|
skill: selectedSkill,
|
||||||
player: newPlayerStats,
|
player: newPlayerStats,
|
||||||
skillSystem: newSkillSystem,
|
skillSystem: newSkillSystem,
|
||||||
|
healingMultiplier: healingMultiplier,
|
||||||
);
|
);
|
||||||
newPlayerStats = skillResult.updatedPlayer;
|
newPlayerStats = skillResult.updatedPlayer;
|
||||||
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
newSkillSystem = skillResult.updatedSkillSystem.startGlobalCooldown();
|
||||||
@@ -499,7 +528,22 @@ class CombatTickService {
|
|||||||
defender: newMonsterStats,
|
defender: newMonsterStats,
|
||||||
);
|
);
|
||||||
newMonsterStats = attackResult.updatedDefender;
|
newMonsterStats = attackResult.updatedDefender;
|
||||||
newTotalDamageDealt += attackResult.result.damage;
|
|
||||||
|
// 첫 공격 배율 적용 (예: Pointer Assassin 1.5배)
|
||||||
|
var damage = attackResult.result.damage;
|
||||||
|
if (isFirstPlayerAttack && firstStrikeBonus > 1.0) {
|
||||||
|
damage = (damage * firstStrikeBonus).round();
|
||||||
|
// 첫 공격 배율이 적용된 데미지로 몬스터 HP 재계산
|
||||||
|
final extraDamage = damage - attackResult.result.damage;
|
||||||
|
if (extraDamage > 0) {
|
||||||
|
final newHp = (newMonsterStats.hpCurrent - extraDamage).clamp(
|
||||||
|
0,
|
||||||
|
newMonsterStats.hpMax,
|
||||||
|
);
|
||||||
|
newMonsterStats = newMonsterStats.copyWith(hpCurrent: newHp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTotalDamageDealt += damage;
|
||||||
|
|
||||||
final result = attackResult.result;
|
final result = attackResult.result;
|
||||||
if (result.isEvaded) {
|
if (result.isEvaded) {
|
||||||
@@ -513,13 +557,35 @@ class CombatTickService {
|
|||||||
events.add(
|
events.add(
|
||||||
CombatEvent.playerAttack(
|
CombatEvent.playerAttack(
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
damage: result.damage,
|
damage: damage,
|
||||||
targetName: newMonsterStats.name,
|
targetName: newMonsterStats.name,
|
||||||
isCritical: result.isCritical,
|
isCritical: result.isCritical,
|
||||||
attackDelayMs: newPlayerStats.attackDelayMs,
|
attackDelayMs: newPlayerStats.attackDelayMs,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 연속 공격 (Refactor Monk 패시브) - 30% 확률로 추가 공격
|
||||||
|
if (hasMultiAttack && newMonsterStats.isAlive && rng.nextDouble() < 0.3) {
|
||||||
|
final extraAttack = calculator.playerAttackMonster(
|
||||||
|
attacker: newPlayerStats,
|
||||||
|
defender: newMonsterStats,
|
||||||
|
);
|
||||||
|
newMonsterStats = extraAttack.updatedDefender;
|
||||||
|
newTotalDamageDealt += extraAttack.result.damage;
|
||||||
|
|
||||||
|
if (!extraAttack.result.isEvaded) {
|
||||||
|
events.add(
|
||||||
|
CombatEvent.playerAttack(
|
||||||
|
timestamp: timestamp,
|
||||||
|
damage: extraAttack.result.damage,
|
||||||
|
targetName: newMonsterStats.name,
|
||||||
|
isCritical: extraAttack.result.isCritical,
|
||||||
|
attackDelayMs: newPlayerStats.attackDelayMs,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -530,6 +596,7 @@ class CombatTickService {
|
|||||||
activeDebuffs: newActiveBuffs,
|
activeDebuffs: newActiveBuffs,
|
||||||
totalDamageDealt: newTotalDamageDealt,
|
totalDamageDealt: newTotalDamageDealt,
|
||||||
events: events,
|
events: events,
|
||||||
|
isFirstPlayerAttack: false, // 첫 공격 이후에는 false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class PotionService {
|
|||||||
/// [maxHp] 최대 HP
|
/// [maxHp] 최대 HP
|
||||||
/// [currentMp] 현재 MP
|
/// [currentMp] 현재 MP
|
||||||
/// [maxMp] 최대 MP
|
/// [maxMp] 최대 MP
|
||||||
|
/// [healingMultiplier] 회복력 배율 (기본 1.0, 클래스 패시브 적용)
|
||||||
PotionUseResult usePotion({
|
PotionUseResult usePotion({
|
||||||
required String potionId,
|
required String potionId,
|
||||||
required PotionInventory inventory,
|
required PotionInventory inventory,
|
||||||
@@ -59,6 +60,7 @@ class PotionService {
|
|||||||
required int maxHp,
|
required int maxHp,
|
||||||
required int currentMp,
|
required int currentMp,
|
||||||
required int maxMp,
|
required int maxMp,
|
||||||
|
double healingMultiplier = 1.0,
|
||||||
}) {
|
}) {
|
||||||
final (canUse, failReason) = canUsePotion(potionId, inventory);
|
final (canUse, failReason) = canUsePotion(potionId, inventory);
|
||||||
if (!canUse) {
|
if (!canUse) {
|
||||||
@@ -71,11 +73,15 @@ class PotionService {
|
|||||||
int newMp = currentMp;
|
int newMp = currentMp;
|
||||||
|
|
||||||
if (potion.isHpPotion) {
|
if (potion.isHpPotion) {
|
||||||
healedAmount = potion.calculateHeal(maxHp);
|
// 회복력 보너스 적용 (예: Debugger Paladin +10%)
|
||||||
|
final baseHeal = potion.calculateHeal(maxHp);
|
||||||
|
healedAmount = (baseHeal * healingMultiplier).round();
|
||||||
newHp = (currentHp + healedAmount).clamp(0, maxHp);
|
newHp = (currentHp + healedAmount).clamp(0, maxHp);
|
||||||
healedAmount = newHp - currentHp; // 실제 회복량
|
healedAmount = newHp - currentHp; // 실제 회복량
|
||||||
} else if (potion.isMpPotion) {
|
} else if (potion.isMpPotion) {
|
||||||
healedAmount = potion.calculateHeal(maxMp);
|
// MP 물약에도 회복력 보너스 적용
|
||||||
|
final baseHeal = potion.calculateHeal(maxMp);
|
||||||
|
healedAmount = (baseHeal * healingMultiplier).round();
|
||||||
newMp = (currentMp + healedAmount).clamp(0, maxMp);
|
newMp = (currentMp + healedAmount).clamp(0, maxMp);
|
||||||
healedAmount = newMp - currentMp; // 실제 회복량
|
healedAmount = newMp - currentMp; // 실제 회복량
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:asciineverdie/data/class_data.dart';
|
||||||
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
import 'package:asciineverdie/data/game_text_l10n.dart' as l10n;
|
||||||
|
import 'package:asciineverdie/data/race_data.dart';
|
||||||
|
import 'package:asciineverdie/src/core/model/class_traits.dart';
|
||||||
import 'package:asciineverdie/src/core/animation/monster_size.dart';
|
import 'package:asciineverdie/src/core/animation/monster_size.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/act_progression_service.dart';
|
import 'package:asciineverdie/src/core/engine/act_progression_service.dart';
|
||||||
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
|
import 'package:asciineverdie/src/core/engine/combat_calculator.dart';
|
||||||
@@ -273,10 +276,21 @@ class ProgressService {
|
|||||||
final remainingHp = combat.playerStats.hpCurrent;
|
final remainingHp = combat.playerStats.hpCurrent;
|
||||||
final maxHp = combat.playerStats.hpMax;
|
final maxHp = combat.playerStats.hpMax;
|
||||||
|
|
||||||
// 전투 승리 시 HP 회복 (50% + CON/2)
|
// 전투 승리 시 HP 회복 (50% + CON/2 + 클래스 패시브)
|
||||||
// 아이들 게임 특성상 전투 사이 HP가 회복되어야 지속 플레이 가능
|
// 아이들 게임 특성상 전투 사이 HP가 회복되어야 지속 플레이 가능
|
||||||
final conBonus = nextState.stats.con ~/ 2;
|
final conBonus = nextState.stats.con ~/ 2;
|
||||||
final healAmount = (maxHp * 0.5).round() + conBonus;
|
var healAmount = (maxHp * 0.5).round() + conBonus;
|
||||||
|
|
||||||
|
// 클래스 패시브: 전투 후 HP 회복 (예: Garbage Collector +5%)
|
||||||
|
final klass = ClassData.findById(nextState.traits.classId);
|
||||||
|
if (klass != null) {
|
||||||
|
final postCombatHealRate =
|
||||||
|
klass.getPassiveValue(ClassPassiveType.postCombatHeal);
|
||||||
|
if (postCombatHealRate > 0) {
|
||||||
|
healAmount += (maxHp * postCombatHealRate).round();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final newHp = (remainingHp + healAmount).clamp(0, maxHp);
|
final newHp = (remainingHp + healAmount).clamp(0, maxHp);
|
||||||
|
|
||||||
nextState = nextState.copyWith(
|
nextState = nextState.copyWith(
|
||||||
@@ -384,7 +398,11 @@ class ProgressService {
|
|||||||
// Gain XP / level up (몬스터 경험치 기반)
|
// Gain XP / level up (몬스터 경험치 기반)
|
||||||
// 최대 레벨(100) 제한: 100레벨에서는 더 이상 레벨업하지 않음
|
// 최대 레벨(100) 제한: 100레벨에서는 더 이상 레벨업하지 않음
|
||||||
if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
|
if (gain && nextState.traits.level < 100 && monsterExpReward > 0) {
|
||||||
final newExpPos = progress.exp.position + monsterExpReward;
|
// 종족 경험치 배율 적용 (예: Byte Human +5%, Callback Seraph +3%)
|
||||||
|
final race = RaceData.findById(nextState.traits.raceId);
|
||||||
|
final expMultiplier = race?.expMultiplier ?? 1.0;
|
||||||
|
final adjustedExp = (monsterExpReward * expMultiplier).round();
|
||||||
|
final newExpPos = progress.exp.position + adjustedExp;
|
||||||
|
|
||||||
// 레벨업 체크 (경험치가 필요량 이상일 때)
|
// 레벨업 체크 (경험치가 필요량 이상일 때)
|
||||||
if (newExpPos >= progress.exp.max) {
|
if (newExpPos >= progress.exp.max) {
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ class SkillService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 회복 스킬 사용
|
/// 회복 스킬 사용
|
||||||
|
///
|
||||||
|
/// [healingMultiplier] 회복력 배율 (기본 1.0, 클래스 패시브 적용)
|
||||||
({
|
({
|
||||||
SkillUseResult result,
|
SkillUseResult result,
|
||||||
CombatStats updatedPlayer,
|
CombatStats updatedPlayer,
|
||||||
@@ -121,6 +123,7 @@ class SkillService {
|
|||||||
required Skill skill,
|
required Skill skill,
|
||||||
required CombatStats player,
|
required CombatStats player,
|
||||||
required SkillSystemState skillSystem,
|
required SkillSystemState skillSystem,
|
||||||
|
double healingMultiplier = 1.0,
|
||||||
}) {
|
}) {
|
||||||
// 회복량 계산
|
// 회복량 계산
|
||||||
int healAmount = skill.healAmount;
|
int healAmount = skill.healAmount;
|
||||||
@@ -128,6 +131,9 @@ class SkillService {
|
|||||||
healAmount += (player.hpMax * skill.healPercent).round();
|
healAmount += (player.hpMax * skill.healPercent).round();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 회복력 보너스 적용 (예: Debugger Paladin +10%, Exception Handler +15%)
|
||||||
|
healAmount = (healAmount * healingMultiplier).round();
|
||||||
|
|
||||||
// HP 회복
|
// HP 회복
|
||||||
var updatedPlayer = player.applyHeal(healAmount);
|
var updatedPlayer = player.applyHeal(healAmount);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user