feat(combat): Phase 1 핵심 전투 시스템 구현
신규 파일: - combat_stats.dart: 플레이어 전투 파생 스탯 (ATK, DEF, CRI 등) - monster_combat_stats.dart: 몬스터 전투 스탯 (레벨 기반 스케일링) - combat_result.dart: 전투 결과 타입 (AttackResult, CombatTurnResult) - combat_state.dart: 전투 상태 관리 (HP, 누적 시간, 턴 수) - combat_calculator.dart: 전투 계산 서비스 (데미지, 명중, 크리티컬) 수정 파일: - game_state.dart: ProgressState에 currentCombat 필드 추가 - progress_service.dart: 킬 태스크 시 전투 로직 통합 - CombatStats/MonsterCombatStats 기반 전투 시간 계산 - 틱마다 전투 턴 처리 (_processCombatTick) - 전투 완료 시 플레이어 HP 반영
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:askiineverdie/data/game_text_l10n.dart' as l10n;
|
||||
import 'package:askiineverdie/src/core/engine/combat_calculator.dart';
|
||||
import 'package:askiineverdie/src/core/engine/game_mutations.dart';
|
||||
import 'package:askiineverdie/src/core/engine/reward_service.dart';
|
||||
import 'package:askiineverdie/src/core/model/combat_state.dart';
|
||||
import 'package:askiineverdie/src/core/model/combat_stats.dart';
|
||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||
import 'package:askiineverdie/src/core/model/monster_combat_stats.dart';
|
||||
import 'package:askiineverdie/src/core/model/pq_config.dart';
|
||||
import 'package:askiineverdie/src/core/util/pq_logic.dart' as pq_logic;
|
||||
|
||||
@@ -143,8 +147,16 @@ class ProgressService {
|
||||
final int newTaskPos = uncapped > progress.task.max
|
||||
? progress.task.max
|
||||
: uncapped;
|
||||
|
||||
// 킬 태스크 중 전투 진행
|
||||
var updatedCombat = progress.currentCombat;
|
||||
if (progress.currentTask.type == TaskType.kill && updatedCombat != null && updatedCombat.isActive) {
|
||||
updatedCombat = _processCombatTick(nextState, updatedCombat, clamped);
|
||||
}
|
||||
|
||||
progress = progress.copyWith(
|
||||
task: progress.task.copyWith(position: newTaskPos),
|
||||
currentCombat: updatedCombat,
|
||||
);
|
||||
nextState = _recalculateEncumbrance(
|
||||
nextState.copyWith(progress: progress),
|
||||
@@ -155,10 +167,24 @@ class ProgressService {
|
||||
final gain = progress.currentTask.type == TaskType.kill;
|
||||
final incrementSeconds = progress.task.max ~/ 1000;
|
||||
|
||||
// 킬 태스크 완료 시 전리품 획득 (원본 Main.pas:625-630)
|
||||
// 킬 태스크 완료 시 전투 결과 반영 및 전리품 획득
|
||||
if (gain) {
|
||||
// 전투 결과에 따라 플레이어 HP 업데이트
|
||||
final combat = progress.currentCombat;
|
||||
if (combat != null && combat.isActive) {
|
||||
// 전투 중 받은 데미지를 실제 Stats에 반영
|
||||
final newHp = combat.playerStats.hpCurrent;
|
||||
nextState = nextState.copyWith(
|
||||
stats: nextState.stats.copyWith(hpCurrent: newHp),
|
||||
);
|
||||
}
|
||||
|
||||
// 전리품 획득 (원본 Main.pas:625-630)
|
||||
nextState = _winLoot(nextState);
|
||||
progress = nextState.progress;
|
||||
|
||||
// 전투 상태 초기화
|
||||
progress = nextState.progress.copyWith(currentCombat: null);
|
||||
nextState = nextState.copyWith(progress: progress);
|
||||
}
|
||||
|
||||
// 시장/판매/구매 태스크 완료 시 처리 (원본 Main.pas:631-649)
|
||||
@@ -367,11 +393,31 @@ class ProgressService {
|
||||
questLevel,
|
||||
);
|
||||
|
||||
// 태스크 지속시간 계산 (원본 682줄)
|
||||
// n := (2 * InventoryLabelAlsoGameStyle.Tag * n * 1000) div l;
|
||||
// InventoryLabelAlsoGameStyle.Tag는 게임 스타일을 나타내는 값 (1이 기본)
|
||||
const gameStyleTag = 1;
|
||||
final durationMillis = (2 * gameStyleTag * level * 1000) ~/ level;
|
||||
// 전투 스탯 생성
|
||||
final playerCombatStats = CombatStats.fromStats(
|
||||
stats: state.stats,
|
||||
equipment: state.equipment,
|
||||
level: level,
|
||||
);
|
||||
|
||||
final monsterCombatStats = MonsterCombatStats.fromLevel(
|
||||
name: monsterResult.displayName,
|
||||
level: monsterResult.level,
|
||||
speedType: MonsterCombatStats.inferSpeedType(monsterResult.baseName),
|
||||
);
|
||||
|
||||
// 전투 상태 초기화
|
||||
final combatState = CombatState.start(
|
||||
playerStats: playerCombatStats,
|
||||
monsterStats: monsterCombatStats,
|
||||
);
|
||||
|
||||
// 태스크 지속시간 계산 (CombatCalculator 기반)
|
||||
final combatCalculator = CombatCalculator(rng: state.rng);
|
||||
final durationMillis = combatCalculator.estimateCombatDurationMs(
|
||||
player: playerCombatStats,
|
||||
monster: monsterCombatStats,
|
||||
);
|
||||
|
||||
final taskResult = pq_logic.startTask(
|
||||
progress,
|
||||
@@ -387,6 +433,7 @@ class ProgressService {
|
||||
monsterPart: monsterResult.part,
|
||||
monsterLevel: monsterResult.level,
|
||||
),
|
||||
currentCombat: combatState,
|
||||
);
|
||||
|
||||
return (progress: progress, queue: queue);
|
||||
@@ -743,4 +790,66 @@ class ProgressService {
|
||||
continuesSelling: false,
|
||||
);
|
||||
}
|
||||
|
||||
/// 전투 틱 처리
|
||||
///
|
||||
/// [state] 현재 게임 상태
|
||||
/// [combat] 현재 전투 상태
|
||||
/// [elapsedMs] 경과 시간 (밀리초)
|
||||
/// Returns: 업데이트된 전투 상태
|
||||
CombatState _processCombatTick(
|
||||
GameState state,
|
||||
CombatState combat,
|
||||
int elapsedMs,
|
||||
) {
|
||||
if (!combat.isActive || combat.isCombatOver) {
|
||||
return combat;
|
||||
}
|
||||
|
||||
final calculator = CombatCalculator(rng: state.rng);
|
||||
var playerStats = combat.playerStats;
|
||||
var monsterStats = combat.monsterStats;
|
||||
var playerAccumulator = combat.playerAttackAccumulatorMs + elapsedMs;
|
||||
var monsterAccumulator = combat.monsterAttackAccumulatorMs + elapsedMs;
|
||||
var totalDamageDealt = combat.totalDamageDealt;
|
||||
var totalDamageTaken = combat.totalDamageTaken;
|
||||
var turnsElapsed = combat.turnsElapsed;
|
||||
|
||||
// 플레이어 공격 체크
|
||||
if (playerAccumulator >= playerStats.attackDelayMs) {
|
||||
final attackResult = calculator.playerAttackMonster(
|
||||
attacker: playerStats,
|
||||
defender: monsterStats,
|
||||
);
|
||||
monsterStats = attackResult.updatedDefender;
|
||||
totalDamageDealt += attackResult.result.damage;
|
||||
playerAccumulator -= playerStats.attackDelayMs;
|
||||
turnsElapsed++;
|
||||
}
|
||||
|
||||
// 몬스터가 살아있으면 반격
|
||||
if (monsterStats.isAlive && monsterAccumulator >= monsterStats.attackDelayMs) {
|
||||
final attackResult = calculator.monsterAttackPlayer(
|
||||
attacker: monsterStats,
|
||||
defender: playerStats,
|
||||
);
|
||||
playerStats = attackResult.updatedDefender;
|
||||
totalDamageTaken += attackResult.result.damage;
|
||||
monsterAccumulator -= monsterStats.attackDelayMs;
|
||||
}
|
||||
|
||||
// 전투 종료 체크
|
||||
final isActive = playerStats.isAlive && monsterStats.isAlive;
|
||||
|
||||
return combat.copyWith(
|
||||
playerStats: playerStats,
|
||||
monsterStats: monsterStats,
|
||||
playerAttackAccumulatorMs: playerAccumulator,
|
||||
monsterAttackAccumulatorMs: monsterAccumulator,
|
||||
totalDamageDealt: totalDamageDealt,
|
||||
totalDamageTaken: totalDamageTaken,
|
||||
turnsElapsed: turnsElapsed,
|
||||
isActive: isActive,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user