feat(game): 게임 시스템 전면 개편 및 다국어 지원 확장
## 스킬 시스템 개선 - skill_data.dart: 스킬 데이터 구조 전면 개편 (+1176 라인) - skill_service.dart: 스킬 발동 로직 확장 및 버프 시스템 연동 - skill.dart: 스킬 모델 개선, 쿨다운/효과 타입 추가 ## Canvas 애니메이션 리팩토링 - battle_composer.dart 삭제 (레거시 위젯 기반 렌더러) - monster_colors.dart 삭제 (AsciiCell 색상 시스템으로 통합) - canvas_battle_composer.dart: z-index 정렬 (몬스터 z=1, 캐릭터 z=2, 이펙트 z=3) - ascii_cell.dart, ascii_layer.dart: 코드 정리 ## UI/UX 개선 - hp_mp_bar.dart: l10n 적용, 몬스터 HP 바 컴팩트화 - death_overlay.dart: 사망 화면 개선 - equipment_stats_panel.dart: 장비 스탯 표시 확장 - active_buff_panel.dart: 버프 패널 개선 - notification_overlay.dart: 알림 시스템 개선 ## 다국어 지원 확장 - game_text_l10n.dart: 게임 텍스트 통합 (+758 라인) - 한국어/일본어/영어/중국어 번역 업데이트 - ARB 파일 동기화 ## 게임 로직 개선 - progress_service.dart: 진행 로직 리팩토링 - combat_calculator.dart: 전투 계산 로직 개선 - stat_calculator.dart: 스탯 계산 시스템 개선 - story_service.dart: 스토리 진행 로직 개선 ## 기타 - theme_preferences.dart 삭제 (미사용) - 테스트 파일 업데이트 - class_data.dart: 클래스 데이터 정리
This commit is contained in:
@@ -112,7 +112,9 @@ class ProgressService {
|
||||
),
|
||||
plotStageCount: 1, // Prologue
|
||||
questCount: 0,
|
||||
plotHistory: [HistoryEntry(caption: l10n.taskPrologue, isComplete: false)],
|
||||
plotHistory: [
|
||||
HistoryEntry(caption: l10n.taskPrologue, isComplete: false),
|
||||
],
|
||||
questHistory: const [],
|
||||
);
|
||||
|
||||
@@ -156,13 +158,17 @@ class ProgressService {
|
||||
|
||||
// 스킬 시스템 시간 업데이트 (Phase 3)
|
||||
final skillService = SkillService(rng: state.rng);
|
||||
var skillSystem = skillService.updateElapsedTime(state.skillSystem, clamped);
|
||||
var skillSystem = skillService.updateElapsedTime(
|
||||
state.skillSystem,
|
||||
clamped,
|
||||
);
|
||||
|
||||
// 만료된 버프 정리
|
||||
skillSystem = skillService.cleanupExpiredBuffs(skillSystem);
|
||||
|
||||
// 비전투 시 MP 회복
|
||||
final isInCombat = progress.currentTask.type == TaskType.kill &&
|
||||
final isInCombat =
|
||||
progress.currentTask.type == TaskType.kill &&
|
||||
progress.currentCombat != null &&
|
||||
progress.currentCombat!.isActive;
|
||||
|
||||
@@ -173,7 +179,10 @@ class ProgressService {
|
||||
wis: nextState.stats.wis,
|
||||
);
|
||||
if (mpRegen > 0) {
|
||||
final newMp = (nextState.stats.mp + mpRegen).clamp(0, nextState.stats.mpMax);
|
||||
final newMp = (nextState.stats.mp + mpRegen).clamp(
|
||||
0,
|
||||
nextState.stats.mpMax,
|
||||
);
|
||||
nextState = nextState.copyWith(
|
||||
stats: nextState.stats.copyWith(mpCurrent: newMp),
|
||||
);
|
||||
@@ -193,7 +202,9 @@ class ProgressService {
|
||||
var updatedCombat = progress.currentCombat;
|
||||
var updatedSkillSystem = nextState.skillSystem;
|
||||
var updatedPotionInventory = nextState.potionInventory;
|
||||
if (progress.currentTask.type == TaskType.kill && updatedCombat != null && updatedCombat.isActive) {
|
||||
if (progress.currentTask.type == TaskType.kill &&
|
||||
updatedCombat != null &&
|
||||
updatedCombat.isActive) {
|
||||
final combatResult = _processCombatTickWithSkills(
|
||||
nextState,
|
||||
updatedCombat,
|
||||
@@ -480,7 +491,8 @@ class ProgressService {
|
||||
final questMonster = state.progress.currentQuestMonster;
|
||||
final questMonsterData = questMonster?.monsterData;
|
||||
final questLevel = questMonsterData != null
|
||||
? int.tryParse(questMonsterData.split('|').elementAtOrNull(1) ?? '') ?? 0
|
||||
? int.tryParse(questMonsterData.split('|').elementAtOrNull(1) ?? '') ??
|
||||
0
|
||||
: null;
|
||||
|
||||
final monsterResult = pq_logic.monsterTask(
|
||||
@@ -501,10 +513,9 @@ class ProgressService {
|
||||
// 전투용 몬스터 레벨 조정 (밸런스)
|
||||
// config의 raw 레벨이 플레이어보다 너무 높으면 전투가 불가능
|
||||
// 플레이어 레벨 ±3 범위로 제한 (최소 1)
|
||||
final effectiveMonsterLevel = monsterResult.level.clamp(
|
||||
math.max(1, level - 3),
|
||||
level + 3,
|
||||
).toInt();
|
||||
final effectiveMonsterLevel = monsterResult.level
|
||||
.clamp(math.max(1, level - 3), level + 3)
|
||||
.toInt();
|
||||
|
||||
final monsterCombatStats = MonsterCombatStats.fromLevel(
|
||||
name: monsterResult.displayName,
|
||||
@@ -907,7 +918,8 @@ class ProgressService {
|
||||
if (hasItemsToSell) {
|
||||
// 다음 아이템 판매 태스크 시작
|
||||
final nextItem = items.first;
|
||||
final itemDesc = l10n.indefiniteL10n(nextItem.name, nextItem.count);
|
||||
final translatedName = l10n.translateItemNameL10n(nextItem.name);
|
||||
final itemDesc = l10n.indefiniteL10n(translatedName, nextItem.count);
|
||||
final taskResult = pq_logic.startTask(
|
||||
state.progress,
|
||||
l10n.taskSelling(itemDesc),
|
||||
@@ -945,7 +957,8 @@ class ProgressService {
|
||||
CombatState combat,
|
||||
SkillSystemState skillSystem,
|
||||
PotionInventory? potionInventory,
|
||||
}) _processCombatTickWithSkills(
|
||||
})
|
||||
_processCombatTickWithSkills(
|
||||
GameState state,
|
||||
CombatState combat,
|
||||
SkillSystemState skillSystem,
|
||||
@@ -988,12 +1001,14 @@ class ProgressService {
|
||||
dotDamageThisTick += damage;
|
||||
|
||||
// DOT 데미지 이벤트 생성
|
||||
newEvents.add(CombatEvent.dotTick(
|
||||
timestamp: timestamp,
|
||||
skillName: dot.skillId,
|
||||
damage: damage,
|
||||
targetName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.dotTick(
|
||||
timestamp: timestamp,
|
||||
skillName: dot.skillId,
|
||||
damage: damage,
|
||||
targetName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 만료되지 않은 DOT만 유지
|
||||
@@ -1004,8 +1019,10 @@ class ProgressService {
|
||||
|
||||
// DOT 데미지 적용
|
||||
if (dotDamageThisTick > 0 && monsterStats.isAlive) {
|
||||
final newMonsterHp = (monsterStats.hpCurrent - dotDamageThisTick)
|
||||
.clamp(0, monsterStats.hpMax);
|
||||
final newMonsterHp = (monsterStats.hpCurrent - dotDamageThisTick).clamp(
|
||||
0,
|
||||
monsterStats.hpMax,
|
||||
);
|
||||
monsterStats = monsterStats.copyWith(hpCurrent: newMonsterHp);
|
||||
totalDamageDealt += dotDamageThisTick;
|
||||
}
|
||||
@@ -1024,8 +1041,7 @@ class ProgressService {
|
||||
playerLevel: state.traits.level,
|
||||
);
|
||||
|
||||
if (emergencyPotion != null &&
|
||||
!usedPotionTypes.contains(PotionType.hp)) {
|
||||
if (emergencyPotion != null && !usedPotionTypes.contains(PotionType.hp)) {
|
||||
final result = potionService.usePotion(
|
||||
potionId: emergencyPotion.id,
|
||||
inventory: state.potionInventory,
|
||||
@@ -1040,25 +1056,27 @@ class ProgressService {
|
||||
usedPotionTypes = {...usedPotionTypes, PotionType.hp};
|
||||
updatedPotionInventory = result.newInventory;
|
||||
|
||||
newEvents.add(CombatEvent.playerPotion(
|
||||
timestamp: timestamp,
|
||||
potionName: emergencyPotion.name,
|
||||
healAmount: result.healedAmount,
|
||||
isHp: true,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerPotion(
|
||||
timestamp: timestamp,
|
||||
potionName: emergencyPotion.name,
|
||||
healAmount: result.healedAmount,
|
||||
isHp: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 플레이어 공격 체크
|
||||
if (playerAccumulator >= playerStats.attackDelayMs) {
|
||||
// 스킬 자동 선택
|
||||
final availableSkillIds = updatedSkillSystem.skillStates
|
||||
.map((s) => s.skillId)
|
||||
.toList();
|
||||
// 기본 스킬이 없으면 기본 스킬 추가
|
||||
// SpellBook에서 사용 가능한 스킬 ID 목록 조회
|
||||
var availableSkillIds = skillService.getAvailableSkillIdsFromSpellBook(
|
||||
state.spellBook,
|
||||
);
|
||||
// SpellBook에 스킬이 없으면 기본 스킬 사용
|
||||
if (availableSkillIds.isEmpty) {
|
||||
availableSkillIds.addAll(SkillData.defaultSkillIds);
|
||||
availableSkillIds = SkillData.defaultSkillIds;
|
||||
}
|
||||
|
||||
final selectedSkill = skillService.selectAutoSkill(
|
||||
@@ -1070,12 +1088,18 @@ class ProgressService {
|
||||
);
|
||||
|
||||
if (selectedSkill != null && selectedSkill.isAttack) {
|
||||
// 공격 스킬 사용
|
||||
final skillResult = skillService.useAttackSkill(
|
||||
// 스펠 랭크 조회 (SpellBook 기반)
|
||||
final spellRank = skillService.getSkillRankFromSpellBook(
|
||||
state.spellBook,
|
||||
selectedSkill.id,
|
||||
);
|
||||
// 랭크 스케일링 적용된 공격 스킬 사용
|
||||
final skillResult = skillService.useAttackSkillWithRank(
|
||||
skill: selectedSkill,
|
||||
player: playerStats,
|
||||
monster: monsterStats,
|
||||
skillSystem: updatedSkillSystem,
|
||||
rank: spellRank,
|
||||
);
|
||||
playerStats = skillResult.updatedPlayer;
|
||||
monsterStats = skillResult.updatedMonster;
|
||||
@@ -1083,12 +1107,14 @@ class ProgressService {
|
||||
updatedSkillSystem = skillResult.updatedSkillSystem;
|
||||
|
||||
// 스킬 공격 이벤트 생성
|
||||
newEvents.add(CombatEvent.playerSkill(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
damage: skillResult.result.damage,
|
||||
targetName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerSkill(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
damage: skillResult.result.damage,
|
||||
targetName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else if (selectedSkill != null && selectedSkill.isDot) {
|
||||
// DOT 스킬 사용
|
||||
final skillResult = skillService.useDotSkill(
|
||||
@@ -1107,12 +1133,14 @@ class ProgressService {
|
||||
}
|
||||
|
||||
// DOT 스킬 사용 이벤트 생성
|
||||
newEvents.add(CombatEvent.playerSkill(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
damage: skillResult.result.damage,
|
||||
targetName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerSkill(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
damage: skillResult.result.damage,
|
||||
targetName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else if (selectedSkill != null && selectedSkill.isHeal) {
|
||||
// 회복 스킬 사용
|
||||
final skillResult = skillService.useHealSkill(
|
||||
@@ -1124,11 +1152,13 @@ class ProgressService {
|
||||
updatedSkillSystem = skillResult.updatedSkillSystem;
|
||||
|
||||
// 회복 이벤트 생성
|
||||
newEvents.add(CombatEvent.playerHeal(
|
||||
timestamp: timestamp,
|
||||
healAmount: skillResult.result.healedAmount,
|
||||
skillName: selectedSkill.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerHeal(
|
||||
timestamp: timestamp,
|
||||
healAmount: skillResult.result.healedAmount,
|
||||
skillName: selectedSkill.name,
|
||||
),
|
||||
);
|
||||
} else if (selectedSkill != null && selectedSkill.isBuff) {
|
||||
// 버프 스킬 사용
|
||||
final skillResult = skillService.useBuffSkill(
|
||||
@@ -1140,10 +1170,12 @@ class ProgressService {
|
||||
updatedSkillSystem = skillResult.updatedSkillSystem;
|
||||
|
||||
// 버프 이벤트 생성
|
||||
newEvents.add(CombatEvent.playerBuff(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerBuff(
|
||||
timestamp: timestamp,
|
||||
skillName: selectedSkill.name,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 일반 공격
|
||||
final attackResult = calculator.playerAttackMonster(
|
||||
@@ -1156,17 +1188,21 @@ class ProgressService {
|
||||
// 일반 공격 이벤트 생성
|
||||
final result = attackResult.result;
|
||||
if (result.isEvaded) {
|
||||
newEvents.add(CombatEvent.monsterEvade(
|
||||
timestamp: timestamp,
|
||||
targetName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.monsterEvade(
|
||||
timestamp: timestamp,
|
||||
targetName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
newEvents.add(CombatEvent.playerAttack(
|
||||
timestamp: timestamp,
|
||||
damage: result.damage,
|
||||
targetName: monsterStats.name,
|
||||
isCritical: result.isCritical,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerAttack(
|
||||
timestamp: timestamp,
|
||||
damage: result.damage,
|
||||
targetName: monsterStats.name,
|
||||
isCritical: result.isCritical,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1175,7 +1211,8 @@ class ProgressService {
|
||||
}
|
||||
|
||||
// 몬스터가 살아있으면 반격
|
||||
if (monsterStats.isAlive && monsterAccumulator >= monsterStats.attackDelayMs) {
|
||||
if (monsterStats.isAlive &&
|
||||
monsterAccumulator >= monsterStats.attackDelayMs) {
|
||||
final attackResult = calculator.monsterAttackPlayer(
|
||||
attacker: monsterStats,
|
||||
defender: playerStats,
|
||||
@@ -1187,28 +1224,36 @@ class ProgressService {
|
||||
// 몬스터 공격 이벤트 생성
|
||||
final result = attackResult.result;
|
||||
if (result.isEvaded) {
|
||||
newEvents.add(CombatEvent.playerEvade(
|
||||
timestamp: timestamp,
|
||||
attackerName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerEvade(
|
||||
timestamp: timestamp,
|
||||
attackerName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else if (result.isBlocked) {
|
||||
newEvents.add(CombatEvent.playerBlock(
|
||||
timestamp: timestamp,
|
||||
reducedDamage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerBlock(
|
||||
timestamp: timestamp,
|
||||
reducedDamage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else if (result.isParried) {
|
||||
newEvents.add(CombatEvent.playerParry(
|
||||
timestamp: timestamp,
|
||||
reducedDamage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.playerParry(
|
||||
timestamp: timestamp,
|
||||
reducedDamage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
newEvents.add(CombatEvent.monsterAttack(
|
||||
timestamp: timestamp,
|
||||
damage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
));
|
||||
newEvents.add(
|
||||
CombatEvent.monsterAttack(
|
||||
timestamp: timestamp,
|
||||
damage: result.damage,
|
||||
attackerName: monsterStats.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,9 +1330,7 @@ class ProgressService {
|
||||
);
|
||||
|
||||
// 전투 상태 초기화
|
||||
final progress = state.progress.copyWith(
|
||||
currentCombat: null,
|
||||
);
|
||||
final progress = state.progress.copyWith(currentCombat: null);
|
||||
|
||||
return state.copyWith(
|
||||
equipment: emptyEquipment,
|
||||
|
||||
Reference in New Issue
Block a user