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:
@@ -4,6 +4,7 @@ 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/skill.dart';
|
||||
import 'package:askiineverdie/src/core/util/deterministic_random.dart';
|
||||
import 'package:askiineverdie/src/core/util/roman.dart';
|
||||
|
||||
/// 스킬 시스템 서비스
|
||||
///
|
||||
@@ -30,7 +31,8 @@ class SkillService {
|
||||
|
||||
// 쿨타임 체크
|
||||
final skillState = skillSystem.getSkillState(skill.id);
|
||||
if (skillState != null && !skillState.isReady(skillSystem.elapsedMs, skill.cooldownMs)) {
|
||||
if (skillState != null &&
|
||||
!skillState.isReady(skillSystem.elapsedMs, skill.cooldownMs)) {
|
||||
return SkillFailReason.onCooldown;
|
||||
}
|
||||
|
||||
@@ -49,7 +51,8 @@ class SkillService {
|
||||
CombatStats updatedPlayer,
|
||||
MonsterCombatStats updatedMonster,
|
||||
SkillSystemState updatedSkillSystem,
|
||||
}) useAttackSkill({
|
||||
})
|
||||
useAttackSkill({
|
||||
required Skill skill,
|
||||
required CombatStats player,
|
||||
required MonsterCombatStats monster,
|
||||
@@ -66,7 +69,9 @@ class SkillService {
|
||||
final effectiveMonsterDef = monster.def * (1 - skill.targetDefReduction);
|
||||
|
||||
// 최종 데미지 계산 (방어력 감산)
|
||||
final finalDamage = (buffedDamage - effectiveMonsterDef * 0.5).round().clamp(1, 9999);
|
||||
final finalDamage = (buffedDamage - effectiveMonsterDef * 0.5)
|
||||
.round()
|
||||
.clamp(1, 9999);
|
||||
|
||||
// 몬스터에 데미지 적용
|
||||
var updatedMonster = monster.applyDamage(finalDamage);
|
||||
@@ -79,17 +84,15 @@ class SkillService {
|
||||
}
|
||||
|
||||
// MP 소모
|
||||
updatedPlayer = updatedPlayer.withMp(updatedPlayer.mpCurrent - skill.mpCost);
|
||||
updatedPlayer = updatedPlayer.withMp(
|
||||
updatedPlayer.mpCurrent - skill.mpCost,
|
||||
);
|
||||
|
||||
// 스킬 상태 업데이트 (쿨타임 시작)
|
||||
final updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);
|
||||
|
||||
return (
|
||||
result: SkillUseResult(
|
||||
skill: skill,
|
||||
success: true,
|
||||
damage: finalDamage,
|
||||
),
|
||||
result: SkillUseResult(skill: skill, success: true, damage: finalDamage),
|
||||
updatedPlayer: updatedPlayer,
|
||||
updatedMonster: updatedMonster,
|
||||
updatedSkillSystem: updatedSkillSystem,
|
||||
@@ -101,7 +104,8 @@ class SkillService {
|
||||
SkillUseResult result,
|
||||
CombatStats updatedPlayer,
|
||||
SkillSystemState updatedSkillSystem,
|
||||
}) useHealSkill({
|
||||
})
|
||||
useHealSkill({
|
||||
required Skill skill,
|
||||
required CombatStats player,
|
||||
required SkillSystemState skillSystem,
|
||||
@@ -116,7 +120,9 @@ class SkillService {
|
||||
var updatedPlayer = player.applyHeal(healAmount);
|
||||
|
||||
// MP 소모
|
||||
updatedPlayer = updatedPlayer.withMp(updatedPlayer.mpCurrent - skill.mpCost);
|
||||
updatedPlayer = updatedPlayer.withMp(
|
||||
updatedPlayer.mpCurrent - skill.mpCost,
|
||||
);
|
||||
|
||||
// 스킬 상태 업데이트
|
||||
final updatedSkillSystem = _updateSkillCooldown(skillSystem, skill.id);
|
||||
@@ -137,7 +143,8 @@ class SkillService {
|
||||
SkillUseResult result,
|
||||
CombatStats updatedPlayer,
|
||||
SkillSystemState updatedSkillSystem,
|
||||
}) useBuffSkill({
|
||||
})
|
||||
useBuffSkill({
|
||||
required Skill skill,
|
||||
required CombatStats player,
|
||||
required SkillSystemState skillSystem,
|
||||
@@ -158,10 +165,11 @@ class SkillService {
|
||||
);
|
||||
|
||||
// 기존 같은 버프 제거 후 새 버프 추가
|
||||
final updatedBuffs = skillSystem.activeBuffs
|
||||
.where((b) => b.effect.id != skill.buff!.id)
|
||||
.toList()
|
||||
..add(newBuff);
|
||||
final updatedBuffs =
|
||||
skillSystem.activeBuffs
|
||||
.where((b) => b.effect.id != skill.buff!.id)
|
||||
.toList()
|
||||
..add(newBuff);
|
||||
|
||||
// MP 소모
|
||||
var updatedPlayer = player.withMp(player.mpCurrent - skill.mpCost);
|
||||
@@ -171,11 +179,7 @@ class SkillService {
|
||||
updatedSkillSystem = updatedSkillSystem.copyWith(activeBuffs: updatedBuffs);
|
||||
|
||||
return (
|
||||
result: SkillUseResult(
|
||||
skill: skill,
|
||||
success: true,
|
||||
appliedBuff: newBuff,
|
||||
),
|
||||
result: SkillUseResult(skill: skill, success: true, appliedBuff: newBuff),
|
||||
updatedPlayer: updatedPlayer,
|
||||
updatedSkillSystem: updatedSkillSystem,
|
||||
);
|
||||
@@ -190,7 +194,8 @@ class SkillService {
|
||||
CombatStats updatedPlayer,
|
||||
SkillSystemState updatedSkillSystem,
|
||||
DotEffect? dotEffect,
|
||||
}) useDotSkill({
|
||||
})
|
||||
useDotSkill({
|
||||
required Skill skill,
|
||||
required CombatStats player,
|
||||
required SkillSystemState skillSystem,
|
||||
@@ -265,12 +270,15 @@ class SkillService {
|
||||
final availableSkills = availableSkillIds
|
||||
.map((id) => SkillData.getSkillById(id))
|
||||
.whereType<Skill>()
|
||||
.where((skill) => canUseSkill(
|
||||
skill: skill,
|
||||
currentMp: currentMp,
|
||||
skillSystem: skillSystem,
|
||||
) ==
|
||||
null)
|
||||
.where(
|
||||
(skill) =>
|
||||
canUseSkill(
|
||||
skill: skill,
|
||||
currentMp: currentMp,
|
||||
skillSystem: skillSystem,
|
||||
) ==
|
||||
null,
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (availableSkills.isEmpty) return null;
|
||||
@@ -311,9 +319,11 @@ class SkillService {
|
||||
|
||||
// 예상 총 데미지 기준 정렬
|
||||
dotSkills.sort((a, b) {
|
||||
final aTotal = (a.baseDotDamage ?? 0) *
|
||||
final aTotal =
|
||||
(a.baseDotDamage ?? 0) *
|
||||
((a.baseDotDurationMs ?? 0) ~/ (a.baseDotTickMs ?? 1000));
|
||||
final bTotal = (b.baseDotDamage ?? 0) *
|
||||
final bTotal =
|
||||
(b.baseDotDamage ?? 0) *
|
||||
((b.baseDotDurationMs ?? 0) ~/ (b.baseDotTickMs ?? 1000));
|
||||
return bTotal.compareTo(aTotal);
|
||||
});
|
||||
@@ -344,7 +354,9 @@ class SkillService {
|
||||
final attackSkills = skills.where((s) => s.isAttack).toList();
|
||||
if (attackSkills.isEmpty) return null;
|
||||
|
||||
attackSkills.sort((a, b) => b.damageMultiplier.compareTo(a.damageMultiplier));
|
||||
attackSkills.sort(
|
||||
(a, b) => b.damageMultiplier.compareTo(a.damageMultiplier),
|
||||
);
|
||||
return attackSkills.first;
|
||||
}
|
||||
|
||||
@@ -399,7 +411,10 @@ class SkillService {
|
||||
// ============================================================================
|
||||
|
||||
/// 스킬 쿨타임 업데이트
|
||||
SkillSystemState _updateSkillCooldown(SkillSystemState state, String skillId) {
|
||||
SkillSystemState _updateSkillCooldown(
|
||||
SkillSystemState state,
|
||||
String skillId,
|
||||
) {
|
||||
final skillStates = List<SkillState>.from(state.skillStates);
|
||||
|
||||
// 기존 상태 찾기
|
||||
@@ -412,11 +427,9 @@ class SkillService {
|
||||
);
|
||||
} else {
|
||||
// 새 상태 추가
|
||||
skillStates.add(SkillState(
|
||||
skillId: skillId,
|
||||
lastUsedMs: state.elapsedMs,
|
||||
rank: 1,
|
||||
));
|
||||
skillStates.add(
|
||||
SkillState(skillId: skillId, lastUsedMs: state.elapsedMs, rank: 1),
|
||||
);
|
||||
}
|
||||
|
||||
return state.copyWith(skillStates: skillStates);
|
||||
@@ -426,4 +439,142 @@ class SkillService {
|
||||
SkillSystemState updateElapsedTime(SkillSystemState state, int deltaMs) {
|
||||
return state.copyWith(elapsedMs: state.elapsedMs + deltaMs);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SpellBook 연동
|
||||
// ============================================================================
|
||||
|
||||
/// SpellBook에서 사용 가능한 스킬 목록 조회
|
||||
///
|
||||
/// SpellEntry 이름을 Skill로 매핑하여 반환
|
||||
List<Skill> getAvailableSkillsFromSpellBook(SpellBook spellBook) {
|
||||
return spellBook.spells
|
||||
.map((spell) => SkillData.getSkillBySpellName(spell.name))
|
||||
.whereType<Skill>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// SpellBook에서 스킬의 랭크(레벨) 조회
|
||||
///
|
||||
/// 로마숫자 랭크(I, II, III)를 정수로 변환하여 반환
|
||||
/// 스펠이 없으면 1 반환
|
||||
int getSkillRankFromSpellBook(SpellBook spellBook, String skillId) {
|
||||
// skillId로 스킬 찾기
|
||||
final skill = SkillData.getSkillById(skillId);
|
||||
if (skill == null) return 1;
|
||||
|
||||
// 스킬 이름으로 SpellEntry 찾기
|
||||
for (final spell in spellBook.spells) {
|
||||
if (spell.name == skill.name) {
|
||||
return romanToInt(spell.rank);
|
||||
}
|
||||
}
|
||||
|
||||
return 1; // 기본 랭크
|
||||
}
|
||||
|
||||
/// SpellBook에서 스킬 ID 목록 조회
|
||||
///
|
||||
/// 전투 시스템에서 사용 가능한 스킬 ID 목록 반환
|
||||
List<String> getAvailableSkillIdsFromSpellBook(SpellBook spellBook) {
|
||||
return getAvailableSkillsFromSpellBook(
|
||||
spellBook,
|
||||
).map((skill) => skill.id).toList();
|
||||
}
|
||||
|
||||
/// 랭크 스케일링이 적용된 공격 스킬 사용
|
||||
///
|
||||
/// [rank] 스펠 랭크 (SpellBook에서 조회)
|
||||
({
|
||||
SkillUseResult result,
|
||||
CombatStats updatedPlayer,
|
||||
MonsterCombatStats updatedMonster,
|
||||
SkillSystemState updatedSkillSystem,
|
||||
})
|
||||
useAttackSkillWithRank({
|
||||
required Skill skill,
|
||||
required CombatStats player,
|
||||
required MonsterCombatStats monster,
|
||||
required SkillSystemState skillSystem,
|
||||
required int rank,
|
||||
}) {
|
||||
// 랭크 스케일링 적용
|
||||
final rankMult = getRankMultiplier(rank);
|
||||
final mpMult = getRankMpMultiplier(rank);
|
||||
|
||||
// 실제 MP 비용 계산
|
||||
final actualMpCost = (skill.mpCost * mpMult).round();
|
||||
|
||||
// 기본 데미지 계산 (랭크 배율 적용)
|
||||
final baseDamage = player.atk * skill.damageMultiplier * rankMult;
|
||||
|
||||
// 버프 효과 적용
|
||||
final buffMods = skillSystem.totalBuffModifiers;
|
||||
final buffedDamage = baseDamage * (1 + buffMods.atkMod);
|
||||
|
||||
// 적 방어력 감소 적용
|
||||
final effectiveMonsterDef = monster.def * (1 - skill.targetDefReduction);
|
||||
|
||||
// 최종 데미지 계산 (방어력 감산)
|
||||
final finalDamage = (buffedDamage - effectiveMonsterDef * 0.5)
|
||||
.round()
|
||||
.clamp(1, 9999);
|
||||
|
||||
// 몬스터에 데미지 적용
|
||||
var updatedMonster = monster.applyDamage(finalDamage);
|
||||
|
||||
// 자해 데미지 적용
|
||||
var updatedPlayer = player;
|
||||
if (skill.selfDamagePercent > 0) {
|
||||
final selfDamage = (player.hpMax * skill.selfDamagePercent).round();
|
||||
updatedPlayer = player.applyDamage(selfDamage);
|
||||
}
|
||||
|
||||
// MP 소모 (랭크 스케일링 적용)
|
||||
updatedPlayer = updatedPlayer.withMp(
|
||||
updatedPlayer.mpCurrent - actualMpCost,
|
||||
);
|
||||
|
||||
// 스킬 상태 업데이트 (쿨타임 시작, 랭크 저장)
|
||||
// 쿨타임 스케일링은 isReady 체크 시 적용됨
|
||||
final updatedSkillSystem = _updateSkillCooldownWithRank(
|
||||
skillSystem,
|
||||
skill.id,
|
||||
rank,
|
||||
);
|
||||
|
||||
return (
|
||||
result: SkillUseResult(skill: skill, success: true, damage: finalDamage),
|
||||
updatedPlayer: updatedPlayer,
|
||||
updatedMonster: updatedMonster,
|
||||
updatedSkillSystem: updatedSkillSystem,
|
||||
);
|
||||
}
|
||||
|
||||
/// 랭크 정보를 포함한 스킬 쿨타임 업데이트
|
||||
SkillSystemState _updateSkillCooldownWithRank(
|
||||
SkillSystemState state,
|
||||
String skillId,
|
||||
int rank,
|
||||
) {
|
||||
final skillStates = List<SkillState>.from(state.skillStates);
|
||||
|
||||
// 기존 상태 찾기
|
||||
final existingIndex = skillStates.indexWhere((s) => s.skillId == skillId);
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
// 기존 상태 업데이트
|
||||
skillStates[existingIndex] = skillStates[existingIndex].copyWith(
|
||||
lastUsedMs: state.elapsedMs,
|
||||
rank: rank,
|
||||
);
|
||||
} else {
|
||||
// 새 상태 추가
|
||||
skillStates.add(
|
||||
SkillState(skillId: skillId, lastUsedMs: state.elapsedMs, rank: rank),
|
||||
);
|
||||
}
|
||||
|
||||
return state.copyWith(skillStates: skillStates);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user