refactor(model): 모델 및 스토리지 정리

- GameState, GameStatistics, HallOfFame 개선
- MonsterGrade, HallOfFameStorage 정리
This commit is contained in:
JiWoong Sul
2026-01-12 16:17:06 +09:00
parent 95528786eb
commit 6f70c18d08
5 changed files with 61 additions and 42 deletions

View File

@@ -763,6 +763,7 @@ class ProgressState {
this.monstersKilled = 0, this.monstersKilled = 0,
this.deathCount = 0, this.deathCount = 0,
this.finalBossState = FinalBossState.notSpawned, this.finalBossState = FinalBossState.notSpawned,
this.pendingActCompletion = false,
}); });
final ProgressBarState task; final ProgressBarState task;
@@ -795,6 +796,9 @@ class ProgressState {
/// 최종 보스 상태 (Act V) /// 최종 보스 상태 (Act V)
final FinalBossState finalBossState; final FinalBossState finalBossState;
/// Act Boss 처치 대기 중 여부 (처치 후 시네마틱 재생 트리거)
final bool pendingActCompletion;
factory ProgressState.empty() => ProgressState( factory ProgressState.empty() => ProgressState(
task: ProgressBarState.empty(), task: ProgressBarState.empty(),
quest: ProgressBarState.empty(), quest: ProgressBarState.empty(),
@@ -826,6 +830,7 @@ class ProgressState {
int? monstersKilled, int? monstersKilled,
int? deathCount, int? deathCount,
FinalBossState? finalBossState, FinalBossState? finalBossState,
bool? pendingActCompletion,
}) { }) {
return ProgressState( return ProgressState(
task: task ?? this.task, task: task ?? this.task,
@@ -843,6 +848,7 @@ class ProgressState {
monstersKilled: monstersKilled ?? this.monstersKilled, monstersKilled: monstersKilled ?? this.monstersKilled,
deathCount: deathCount ?? this.deathCount, deathCount: deathCount ?? this.deathCount,
finalBossState: finalBossState ?? this.finalBossState, finalBossState: finalBossState ?? this.finalBossState,
pendingActCompletion: pendingActCompletion ?? this.pendingActCompletion,
); );
} }
} }

View File

@@ -2,10 +2,7 @@
/// ///
/// 세션 및 누적 통계를 추적하는 모델 /// 세션 및 누적 통계를 추적하는 모델
class GameStatistics { class GameStatistics {
const GameStatistics({ const GameStatistics({required this.session, required this.cumulative});
required this.session,
required this.cumulative,
});
/// 현재 세션 통계 /// 현재 세션 통계
final SessionStatistics session; final SessionStatistics session;
@@ -47,10 +44,7 @@ class GameStatistics {
/// JSON 직렬화 /// JSON 직렬화
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {'session': session.toJson(), 'cumulative': cumulative.toJson()};
'session': session.toJson(),
'cumulative': cumulative.toJson(),
};
} }
/// JSON 역직렬화 /// JSON 역직렬화
@@ -212,8 +206,7 @@ class SessionStatistics {
/// 스킬 사용 기록 /// 스킬 사용 기록
SessionStatistics recordSkillUse({required bool isCritical}) { SessionStatistics recordSkillUse({required bool isCritical}) {
final newCriticalStreak = final newCriticalStreak = isCritical ? currentCriticalStreak + 1 : 0;
isCritical ? currentCriticalStreak + 1 : 0;
final newMaxStreak = newCriticalStreak > maxCriticalStreak final newMaxStreak = newCriticalStreak > maxCriticalStreak
? newCriticalStreak ? newCriticalStreak
: maxCriticalStreak; : maxCriticalStreak;
@@ -227,10 +220,7 @@ class SessionStatistics {
} }
/// 데미지 기록 /// 데미지 기록
SessionStatistics recordDamage({ SessionStatistics recordDamage({int dealt = 0, int taken = 0}) {
int dealt = 0,
int taken = 0,
}) {
return copyWith( return copyWith(
totalDamageDealt: totalDamageDealt + dealt, totalDamageDealt: totalDamageDealt + dealt,
totalDamageTaken: totalDamageTaken + taken, totalDamageTaken: totalDamageTaken + taken,

View File

@@ -19,6 +19,7 @@ class HallOfFameEntry {
required this.monstersKilled, required this.monstersKilled,
required this.questsCompleted, required this.questsCompleted,
required this.clearedAt, required this.clearedAt,
this.raceId = '',
this.finalStats, this.finalStats,
this.finalEquipment, this.finalEquipment,
this.finalSkills, this.finalSkills,
@@ -30,9 +31,12 @@ class HallOfFameEntry {
/// 캐릭터 이름 /// 캐릭터 이름
final String characterName; final String characterName;
/// 종족 /// 종족 (표시용 이름)
final String race; final String race;
/// 종족 ID (ASCII 캐릭터 프레임용)
final String raceId;
/// 클래스 /// 클래스
final String klass; final String klass;
@@ -87,6 +91,7 @@ class HallOfFameEntry {
String? id, String? id,
String? characterName, String? characterName,
String? race, String? race,
String? raceId,
String? klass, String? klass,
int? level, int? level,
int? totalPlayTimeMs, int? totalPlayTimeMs,
@@ -102,6 +107,7 @@ class HallOfFameEntry {
id: id ?? this.id, id: id ?? this.id,
characterName: characterName ?? this.characterName, characterName: characterName ?? this.characterName,
race: race ?? this.race, race: race ?? this.race,
raceId: raceId ?? this.raceId,
klass: klass ?? this.klass, klass: klass ?? this.klass,
level: level ?? this.level, level: level ?? this.level,
totalPlayTimeMs: totalPlayTimeMs ?? this.totalPlayTimeMs, totalPlayTimeMs: totalPlayTimeMs ?? this.totalPlayTimeMs,
@@ -126,6 +132,7 @@ class HallOfFameEntry {
id: DateTime.now().millisecondsSinceEpoch.toString(), id: DateTime.now().millisecondsSinceEpoch.toString(),
characterName: state.traits.name, characterName: state.traits.name,
race: state.traits.race, race: state.traits.race,
raceId: state.traits.raceId,
klass: state.traits.klass, klass: state.traits.klass,
level: state.traits.level, level: state.traits.level,
totalPlayTimeMs: state.skillSystem.elapsedMs, totalPlayTimeMs: state.skillSystem.elapsedMs,
@@ -147,6 +154,7 @@ class HallOfFameEntry {
'id': id, 'id': id,
'characterName': characterName, 'characterName': characterName,
'race': race, 'race': race,
'raceId': raceId,
'klass': klass, 'klass': klass,
'level': level, 'level': level,
'totalPlayTimeMs': totalPlayTimeMs, 'totalPlayTimeMs': totalPlayTimeMs,
@@ -166,6 +174,7 @@ class HallOfFameEntry {
id: json['id'] as String, id: json['id'] as String,
characterName: json['characterName'] as String, characterName: json['characterName'] as String,
race: json['race'] as String, race: json['race'] as String,
raceId: json['raceId'] as String? ?? '',
klass: json['klass'] as String, klass: json['klass'] as String,
level: json['level'] as int, level: json['level'] as int,
totalPlayTimeMs: json['totalPlayTimeMs'] as int, totalPlayTimeMs: json['totalPlayTimeMs'] as int,
@@ -178,13 +187,13 @@ class HallOfFameEntry {
: null, : null,
finalEquipment: json['finalEquipment'] != null finalEquipment: json['finalEquipment'] != null
? (json['finalEquipment'] as List<dynamic>) ? (json['finalEquipment'] as List<dynamic>)
.map((e) => EquipmentItem.fromJson(e as Map<String, dynamic>)) .map((e) => EquipmentItem.fromJson(e as Map<String, dynamic>))
.toList() .toList()
: null, : null,
finalSkills: json['finalSkills'] != null finalSkills: json['finalSkills'] != null
? (json['finalSkills'] as List<dynamic>) ? (json['finalSkills'] as List<dynamic>)
.map((s) => Map<String, String>.from(s as Map)) .map((s) => Map<String, String>.from(s as Map))
.toList() .toList()
: null, : null,
); );
} }
@@ -232,6 +241,12 @@ class HallOfFame {
return HallOfFame(entries: newEntries); return HallOfFame(entries: newEntries);
} }
/// 엔트리 삭제 (디버그용)
HallOfFame removeEntry(String id) {
final newEntries = entries.where((e) => e.id != id).toList();
return HallOfFame(entries: newEntries);
}
/// JSON으로 직렬화 /// JSON으로 직렬화
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return {'entries': entries.map((e) => e.toJson()).toList()}; return {'entries': entries.map((e) => e.toJson()).toList()};
@@ -258,7 +273,8 @@ extension HallOfFameArenaX on HallOfFame {
final levelScore = entry.level * 100; final levelScore = entry.level * 100;
// 2. 장비 점수 (전체 슬롯 합계) // 2. 장비 점수 (전체 슬롯 합계)
final equipScore = entry.finalEquipment?.fold<int>( final equipScore =
entry.finalEquipment?.fold<int>(
0, 0,
(sum, item) => sum + ItemService.calculateEquipmentScore(item), (sum, item) => sum + ItemService.calculateEquipmentScore(item),
) ?? ) ??
@@ -267,8 +283,8 @@ extension HallOfFameArenaX on HallOfFame {
// 3. 전투력 점수 (ATK + DEF + HP/10) // 3. 전투력 점수 (ATK + DEF + HP/10)
final combatScore = entry.finalStats != null final combatScore = entry.finalStats != null
? (entry.finalStats!.atk + ? (entry.finalStats!.atk +
entry.finalStats!.def + entry.finalStats!.def +
entry.finalStats!.hpMax ~/ 10) entry.finalStats!.hpMax ~/ 10)
: 0; : 0;
return levelScore + equipScore + (combatScore ~/ 10); return levelScore + equipScore + (combatScore ~/ 10);

View File

@@ -22,39 +22,39 @@ enum MonsterGrade {
extension MonsterGradeExtension on MonsterGrade { extension MonsterGradeExtension on MonsterGrade {
/// 물약 드랍 확률 보너스 (0.0 ~ 1.0) /// 물약 드랍 확률 보너스 (0.0 ~ 1.0)
double get potionDropBonus => switch (this) { double get potionDropBonus => switch (this) {
MonsterGrade.normal => 0.0, MonsterGrade.normal => 0.0,
MonsterGrade.elite => 0.05, // +5% MonsterGrade.elite => 0.05, // +5%
MonsterGrade.boss => 0.15, // +15% MonsterGrade.boss => 0.15, // +15%
}; };
/// UI 표시용 접두사 (몬스터 이름 앞에 붙음) /// UI 표시용 접두사 (몬스터 이름 앞에 붙음)
String get displayPrefix => switch (this) { String get displayPrefix => switch (this) {
MonsterGrade.normal => '', MonsterGrade.normal => '',
MonsterGrade.elite => '', MonsterGrade.elite => '',
MonsterGrade.boss => '★★★ ', MonsterGrade.boss => '★★★ ',
}; };
/// 스탯 배율 (전투력 강화) /// 스탯 배율 (전투력 강화)
double get statMultiplier => switch (this) { double get statMultiplier => switch (this) {
MonsterGrade.normal => 1.0, MonsterGrade.normal => 1.0,
MonsterGrade.elite => 1.3, // +30% 스탯 MonsterGrade.elite => 1.3, // +30% 스탯
MonsterGrade.boss => 1.8, // +80% 스탯 MonsterGrade.boss => 1.8, // +80% 스탯
}; };
/// 경험치 배율 /// 경험치 배율
double get expMultiplier => switch (this) { double get expMultiplier => switch (this) {
MonsterGrade.normal => 1.0, MonsterGrade.normal => 1.0,
MonsterGrade.elite => 1.5, // +50% 경험치 MonsterGrade.elite => 1.5, // +50% 경험치
MonsterGrade.boss => 2.5, // +150% 경험치 MonsterGrade.boss => 2.5, // +150% 경험치
}; };
/// UI 표시용 색상 /// UI 표시용 색상
/// - Normal: 기본 텍스트 색상 (null 반환 → 기본 스타일 사용) /// - Normal: 기본 텍스트 색상 (null 반환 → 기본 스타일 사용)
/// - Elite: 파란색 (#7AA2F7) /// - Elite: 파란색 (#7AA2F7)
/// - Boss: 금색 (#E0AF68) /// - Boss: 금색 (#E0AF68)
Color? get displayColor => switch (this) { Color? get displayColor => switch (this) {
MonsterGrade.normal => null, MonsterGrade.normal => null,
MonsterGrade.elite => const Color(0xFF7AA2F7), // MP 파랑 MonsterGrade.elite => const Color(0xFF7AA2F7), // MP 파랑
MonsterGrade.boss => const Color(0xFFE0AF68), // 골드 MonsterGrade.boss => const Color(0xFFE0AF68), // 골드
}; };
} }

View File

@@ -64,6 +64,13 @@ class HallOfFameStorage {
return save(updated); return save(updated);
} }
/// 엔트리 삭제 및 저장 (디버그용)
Future<bool> deleteEntry(String id) async {
final hallOfFame = await load();
final updated = hallOfFame.removeEntry(id);
return save(updated);
}
/// 명예의 전당 초기화 (테스트용) /// 명예의 전당 초기화 (테스트용)
Future<bool> clear() async { Future<bool> clear() async {
try { try {