diff --git a/lib/src/core/model/cumulative_statistics.dart b/lib/src/core/model/cumulative_statistics.dart new file mode 100644 index 0000000..f8e139a --- /dev/null +++ b/lib/src/core/model/cumulative_statistics.dart @@ -0,0 +1,276 @@ +import 'package:asciineverdie/src/core/model/session_statistics.dart'; + +/// 누적 통계 (Cumulative Statistics) +/// +/// GameStatistics에서 분리된 모든 게임 세션의 누적 통계 모델. +class CumulativeStatistics { + const CumulativeStatistics({ + required this.totalPlayTimeMs, + required this.totalMonstersKilled, + required this.totalGoldEarned, + required this.totalGoldSpent, + required this.totalSkillsUsed, + required this.totalCriticalHits, + required this.bestCriticalStreak, + required this.totalDamageDealt, + required this.totalDamageTaken, + required this.totalPotionsUsed, + required this.totalItemsSold, + required this.totalQuestsCompleted, + required this.totalDeaths, + required this.totalBossesDefeated, + required this.totalLevelUps, + required this.highestLevel, + required this.highestGoldHeld, + required this.gamesCompleted, + required this.gamesStarted, + }); + + /// 총 플레이 시간 (밀리초) + final int totalPlayTimeMs; + + /// 총 처치한 몬스터 수 + final int totalMonstersKilled; + + /// 총 획득한 골드 + final int totalGoldEarned; + + /// 총 소비한 골드 + final int totalGoldSpent; + + /// 총 스킬 사용 횟수 + final int totalSkillsUsed; + + /// 총 크리티컬 히트 횟수 + final int totalCriticalHits; + + /// 최고 연속 크리티컬 + final int bestCriticalStreak; + + /// 총 입힌 데미지 + final int totalDamageDealt; + + /// 총 받은 데미지 + final int totalDamageTaken; + + /// 총 사용한 물약 수 + final int totalPotionsUsed; + + /// 총 판매한 아이템 수 + final int totalItemsSold; + + /// 총 완료한 퀘스트 수 + final int totalQuestsCompleted; + + /// 총 사망 횟수 + final int totalDeaths; + + /// 총 처치한 보스 수 + final int totalBossesDefeated; + + /// 총 레벨업 횟수 + final int totalLevelUps; + + /// 최고 달성 레벨 + final int highestLevel; + + /// 최대 보유 골드 + final int highestGoldHeld; + + /// 클리어한 게임 수 + final int gamesCompleted; + + /// 시작한 게임 수 + final int gamesStarted; + + /// 총 플레이 시간 Duration + Duration get totalPlayTime => Duration(milliseconds: totalPlayTimeMs); + + /// 총 플레이 시간 포맷 (HH:MM:SS) + String get formattedTotalPlayTime { + final hours = totalPlayTime.inHours; + final minutes = totalPlayTime.inMinutes % 60; + final seconds = totalPlayTime.inSeconds % 60; + return '${hours.toString().padLeft(2, '0')}:' + '${minutes.toString().padLeft(2, '0')}:' + '${seconds.toString().padLeft(2, '0')}'; + } + + /// 평균 게임당 플레이 시간 + Duration get averagePlayTimePerGame { + if (gamesStarted <= 0) return Duration.zero; + return Duration(milliseconds: totalPlayTimeMs ~/ gamesStarted); + } + + /// 게임 완료율 + double get completionRate { + if (gamesStarted <= 0) return 0; + return gamesCompleted / gamesStarted; + } + + /// 빈 누적 통계 + factory CumulativeStatistics.empty() => const CumulativeStatistics( + totalPlayTimeMs: 0, + totalMonstersKilled: 0, + totalGoldEarned: 0, + totalGoldSpent: 0, + totalSkillsUsed: 0, + totalCriticalHits: 0, + bestCriticalStreak: 0, + totalDamageDealt: 0, + totalDamageTaken: 0, + totalPotionsUsed: 0, + totalItemsSold: 0, + totalQuestsCompleted: 0, + totalDeaths: 0, + totalBossesDefeated: 0, + totalLevelUps: 0, + highestLevel: 0, + highestGoldHeld: 0, + gamesCompleted: 0, + gamesStarted: 0, + ); + + /// 세션 통계 병합 + CumulativeStatistics mergeSession(SessionStatistics session) { + return CumulativeStatistics( + totalPlayTimeMs: totalPlayTimeMs + session.playTimeMs, + totalMonstersKilled: totalMonstersKilled + session.monstersKilled, + totalGoldEarned: totalGoldEarned + session.goldEarned, + totalGoldSpent: totalGoldSpent + session.goldSpent, + totalSkillsUsed: totalSkillsUsed + session.skillsUsed, + totalCriticalHits: totalCriticalHits + session.criticalHits, + bestCriticalStreak: session.maxCriticalStreak > bestCriticalStreak + ? session.maxCriticalStreak + : bestCriticalStreak, + totalDamageDealt: totalDamageDealt + session.totalDamageDealt, + totalDamageTaken: totalDamageTaken + session.totalDamageTaken, + totalPotionsUsed: totalPotionsUsed + session.potionsUsed, + totalItemsSold: totalItemsSold + session.itemsSold, + totalQuestsCompleted: totalQuestsCompleted + session.questsCompleted, + totalDeaths: totalDeaths + session.deathCount, + totalBossesDefeated: totalBossesDefeated + session.bossesDefeated, + totalLevelUps: totalLevelUps + session.levelUps, + highestLevel: highestLevel, + highestGoldHeld: highestGoldHeld, + gamesCompleted: gamesCompleted, + gamesStarted: gamesStarted, + ); + } + + /// 최고 레벨 업데이트 + CumulativeStatistics updateHighestLevel(int level) { + if (level <= highestLevel) return this; + return copyWith(highestLevel: level); + } + + /// 최대 골드 업데이트 + CumulativeStatistics updateHighestGold(int gold) { + if (gold <= highestGoldHeld) return this; + return copyWith(highestGoldHeld: gold); + } + + /// 새 게임 시작 기록 + CumulativeStatistics recordGameStart() { + return copyWith(gamesStarted: gamesStarted + 1); + } + + /// 게임 클리어 기록 + CumulativeStatistics recordGameComplete() { + return copyWith(gamesCompleted: gamesCompleted + 1); + } + + CumulativeStatistics copyWith({ + int? totalPlayTimeMs, + int? totalMonstersKilled, + int? totalGoldEarned, + int? totalGoldSpent, + int? totalSkillsUsed, + int? totalCriticalHits, + int? bestCriticalStreak, + int? totalDamageDealt, + int? totalDamageTaken, + int? totalPotionsUsed, + int? totalItemsSold, + int? totalQuestsCompleted, + int? totalDeaths, + int? totalBossesDefeated, + int? totalLevelUps, + int? highestLevel, + int? highestGoldHeld, + int? gamesCompleted, + int? gamesStarted, + }) { + return CumulativeStatistics( + totalPlayTimeMs: totalPlayTimeMs ?? this.totalPlayTimeMs, + totalMonstersKilled: totalMonstersKilled ?? this.totalMonstersKilled, + totalGoldEarned: totalGoldEarned ?? this.totalGoldEarned, + totalGoldSpent: totalGoldSpent ?? this.totalGoldSpent, + totalSkillsUsed: totalSkillsUsed ?? this.totalSkillsUsed, + totalCriticalHits: totalCriticalHits ?? this.totalCriticalHits, + bestCriticalStreak: bestCriticalStreak ?? this.bestCriticalStreak, + totalDamageDealt: totalDamageDealt ?? this.totalDamageDealt, + totalDamageTaken: totalDamageTaken ?? this.totalDamageTaken, + totalPotionsUsed: totalPotionsUsed ?? this.totalPotionsUsed, + totalItemsSold: totalItemsSold ?? this.totalItemsSold, + totalQuestsCompleted: totalQuestsCompleted ?? this.totalQuestsCompleted, + totalDeaths: totalDeaths ?? this.totalDeaths, + totalBossesDefeated: totalBossesDefeated ?? this.totalBossesDefeated, + totalLevelUps: totalLevelUps ?? this.totalLevelUps, + highestLevel: highestLevel ?? this.highestLevel, + highestGoldHeld: highestGoldHeld ?? this.highestGoldHeld, + gamesCompleted: gamesCompleted ?? this.gamesCompleted, + gamesStarted: gamesStarted ?? this.gamesStarted, + ); + } + + /// JSON 직렬화 + Map toJson() { + return { + 'totalPlayTimeMs': totalPlayTimeMs, + 'totalMonstersKilled': totalMonstersKilled, + 'totalGoldEarned': totalGoldEarned, + 'totalGoldSpent': totalGoldSpent, + 'totalSkillsUsed': totalSkillsUsed, + 'totalCriticalHits': totalCriticalHits, + 'bestCriticalStreak': bestCriticalStreak, + 'totalDamageDealt': totalDamageDealt, + 'totalDamageTaken': totalDamageTaken, + 'totalPotionsUsed': totalPotionsUsed, + 'totalItemsSold': totalItemsSold, + 'totalQuestsCompleted': totalQuestsCompleted, + 'totalDeaths': totalDeaths, + 'totalBossesDefeated': totalBossesDefeated, + 'totalLevelUps': totalLevelUps, + 'highestLevel': highestLevel, + 'highestGoldHeld': highestGoldHeld, + 'gamesCompleted': gamesCompleted, + 'gamesStarted': gamesStarted, + }; + } + + /// JSON 역직렬화 + factory CumulativeStatistics.fromJson(Map json) { + return CumulativeStatistics( + totalPlayTimeMs: json['totalPlayTimeMs'] as int? ?? 0, + totalMonstersKilled: json['totalMonstersKilled'] as int? ?? 0, + totalGoldEarned: json['totalGoldEarned'] as int? ?? 0, + totalGoldSpent: json['totalGoldSpent'] as int? ?? 0, + totalSkillsUsed: json['totalSkillsUsed'] as int? ?? 0, + totalCriticalHits: json['totalCriticalHits'] as int? ?? 0, + bestCriticalStreak: json['bestCriticalStreak'] as int? ?? 0, + totalDamageDealt: json['totalDamageDealt'] as int? ?? 0, + totalDamageTaken: json['totalDamageTaken'] as int? ?? 0, + totalPotionsUsed: json['totalPotionsUsed'] as int? ?? 0, + totalItemsSold: json['totalItemsSold'] as int? ?? 0, + totalQuestsCompleted: json['totalQuestsCompleted'] as int? ?? 0, + totalDeaths: json['totalDeaths'] as int? ?? 0, + totalBossesDefeated: json['totalBossesDefeated'] as int? ?? 0, + totalLevelUps: json['totalLevelUps'] as int? ?? 0, + highestLevel: json['highestLevel'] as int? ?? 0, + highestGoldHeld: json['highestGoldHeld'] as int? ?? 0, + gamesCompleted: json['gamesCompleted'] as int? ?? 0, + gamesStarted: json['gamesStarted'] as int? ?? 0, + ); + } +} diff --git a/lib/src/core/model/game_statistics.dart b/lib/src/core/model/game_statistics.dart index e91fd24..fb56e38 100644 --- a/lib/src/core/model/game_statistics.dart +++ b/lib/src/core/model/game_statistics.dart @@ -1,6 +1,14 @@ +import 'package:asciineverdie/src/core/model/cumulative_statistics.dart'; +import 'package:asciineverdie/src/core/model/session_statistics.dart'; + +// 하위 호환성(backward compatibility)을 위한 re-export +export 'package:asciineverdie/src/core/model/cumulative_statistics.dart'; +export 'package:asciineverdie/src/core/model/session_statistics.dart'; + /// 게임 통계 (Game Statistics) /// -/// 세션 및 누적 통계를 추적하는 모델 +/// 세션 및 누적 통계를 추적하는 모델. +/// 세부 구현은 SessionStatistics와 CumulativeStatistics로 분리됨. class GameStatistics { const GameStatistics({required this.session, required this.cumulative}); @@ -59,558 +67,3 @@ class GameStatistics { ); } } - -/// 세션 통계 (Session Statistics) -/// -/// 현재 게임 세션의 통계 -class SessionStatistics { - const SessionStatistics({ - required this.playTimeMs, - required this.monstersKilled, - required this.goldEarned, - required this.goldSpent, - required this.skillsUsed, - required this.criticalHits, - required this.maxCriticalStreak, - required this.currentCriticalStreak, - required this.totalDamageDealt, - required this.totalDamageTaken, - required this.potionsUsed, - required this.itemsSold, - required this.questsCompleted, - required this.deathCount, - required this.bossesDefeated, - required this.levelUps, - }); - - /// 플레이 시간 (밀리초) - final int playTimeMs; - - /// 처치한 몬스터 수 - final int monstersKilled; - - /// 획득한 골드 총량 - final int goldEarned; - - /// 소비한 골드 총량 - final int goldSpent; - - /// 사용한 스킬 횟수 - final int skillsUsed; - - /// 크리티컬 히트 횟수 - final int criticalHits; - - /// 최대 연속 크리티컬 - final int maxCriticalStreak; - - /// 현재 연속 크리티컬 (내부 추적용) - final int currentCriticalStreak; - - /// 총 입힌 데미지 - final int totalDamageDealt; - - /// 총 받은 데미지 - final int totalDamageTaken; - - /// 사용한 물약 수 - final int potionsUsed; - - /// 판매한 아이템 수 - final int itemsSold; - - /// 완료한 퀘스트 수 - final int questsCompleted; - - /// 사망 횟수 - final int deathCount; - - /// 처치한 보스 수 - final int bossesDefeated; - - /// 레벨업 횟수 - final int levelUps; - - /// 플레이 시간 Duration - Duration get playTime => Duration(milliseconds: playTimeMs); - - /// 플레이 시간 포맷 (HH:MM:SS) - String get formattedPlayTime { - final hours = playTime.inHours; - final minutes = playTime.inMinutes % 60; - final seconds = playTime.inSeconds % 60; - return '${hours.toString().padLeft(2, '0')}:' - '${minutes.toString().padLeft(2, '0')}:' - '${seconds.toString().padLeft(2, '0')}'; - } - - /// 평균 DPS (damage per second) - double get averageDps { - if (playTimeMs <= 0) return 0; - return totalDamageDealt / (playTimeMs / 1000); - } - - /// 킬당 평균 골드 - double get goldPerKill { - if (monstersKilled <= 0) return 0; - return goldEarned / monstersKilled; - } - - /// 크리티컬 비율 - double get criticalRate { - if (skillsUsed <= 0) return 0; - return criticalHits / skillsUsed; - } - - /// 빈 세션 통계 - factory SessionStatistics.empty() => const SessionStatistics( - playTimeMs: 0, - monstersKilled: 0, - goldEarned: 0, - goldSpent: 0, - skillsUsed: 0, - criticalHits: 0, - maxCriticalStreak: 0, - currentCriticalStreak: 0, - totalDamageDealt: 0, - totalDamageTaken: 0, - potionsUsed: 0, - itemsSold: 0, - questsCompleted: 0, - deathCount: 0, - bossesDefeated: 0, - levelUps: 0, - ); - - // ============================================================================ - // 이벤트 기록 메서드 - // ============================================================================ - - /// 몬스터 처치 기록 - SessionStatistics recordKill({bool isBoss = false}) { - return copyWith( - monstersKilled: monstersKilled + 1, - bossesDefeated: isBoss ? bossesDefeated + 1 : bossesDefeated, - ); - } - - /// 골드 획득 기록 - SessionStatistics recordGoldEarned(int amount) { - return copyWith(goldEarned: goldEarned + amount); - } - - /// 골드 소비 기록 - SessionStatistics recordGoldSpent(int amount) { - return copyWith(goldSpent: goldSpent + amount); - } - - /// 스킬 사용 기록 - SessionStatistics recordSkillUse({required bool isCritical}) { - final newCriticalStreak = isCritical ? currentCriticalStreak + 1 : 0; - final newMaxStreak = newCriticalStreak > maxCriticalStreak - ? newCriticalStreak - : maxCriticalStreak; - - return copyWith( - skillsUsed: skillsUsed + 1, - criticalHits: isCritical ? criticalHits + 1 : criticalHits, - currentCriticalStreak: newCriticalStreak, - maxCriticalStreak: newMaxStreak, - ); - } - - /// 데미지 기록 - SessionStatistics recordDamage({int dealt = 0, int taken = 0}) { - return copyWith( - totalDamageDealt: totalDamageDealt + dealt, - totalDamageTaken: totalDamageTaken + taken, - ); - } - - /// 물약 사용 기록 - SessionStatistics recordPotionUse() { - return copyWith(potionsUsed: potionsUsed + 1); - } - - /// 아이템 판매 기록 - SessionStatistics recordItemSold(int count) { - return copyWith(itemsSold: itemsSold + count); - } - - /// 퀘스트 완료 기록 - SessionStatistics recordQuestComplete() { - return copyWith(questsCompleted: questsCompleted + 1); - } - - /// 사망 기록 - SessionStatistics recordDeath() { - return copyWith(deathCount: deathCount + 1); - } - - /// 레벨업 기록 - SessionStatistics recordLevelUp() { - return copyWith(levelUps: levelUps + 1); - } - - /// 플레이 시간 업데이트 - SessionStatistics updatePlayTime(int elapsedMs) { - return copyWith(playTimeMs: elapsedMs); - } - - SessionStatistics copyWith({ - int? playTimeMs, - int? monstersKilled, - int? goldEarned, - int? goldSpent, - int? skillsUsed, - int? criticalHits, - int? maxCriticalStreak, - int? currentCriticalStreak, - int? totalDamageDealt, - int? totalDamageTaken, - int? potionsUsed, - int? itemsSold, - int? questsCompleted, - int? deathCount, - int? bossesDefeated, - int? levelUps, - }) { - return SessionStatistics( - playTimeMs: playTimeMs ?? this.playTimeMs, - monstersKilled: monstersKilled ?? this.monstersKilled, - goldEarned: goldEarned ?? this.goldEarned, - goldSpent: goldSpent ?? this.goldSpent, - skillsUsed: skillsUsed ?? this.skillsUsed, - criticalHits: criticalHits ?? this.criticalHits, - maxCriticalStreak: maxCriticalStreak ?? this.maxCriticalStreak, - currentCriticalStreak: - currentCriticalStreak ?? this.currentCriticalStreak, - totalDamageDealt: totalDamageDealt ?? this.totalDamageDealt, - totalDamageTaken: totalDamageTaken ?? this.totalDamageTaken, - potionsUsed: potionsUsed ?? this.potionsUsed, - itemsSold: itemsSold ?? this.itemsSold, - questsCompleted: questsCompleted ?? this.questsCompleted, - deathCount: deathCount ?? this.deathCount, - bossesDefeated: bossesDefeated ?? this.bossesDefeated, - levelUps: levelUps ?? this.levelUps, - ); - } - - /// JSON 직렬화 - Map toJson() { - return { - 'playTimeMs': playTimeMs, - 'monstersKilled': monstersKilled, - 'goldEarned': goldEarned, - 'goldSpent': goldSpent, - 'skillsUsed': skillsUsed, - 'criticalHits': criticalHits, - 'maxCriticalStreak': maxCriticalStreak, - 'totalDamageDealt': totalDamageDealt, - 'totalDamageTaken': totalDamageTaken, - 'potionsUsed': potionsUsed, - 'itemsSold': itemsSold, - 'questsCompleted': questsCompleted, - 'deathCount': deathCount, - 'bossesDefeated': bossesDefeated, - 'levelUps': levelUps, - }; - } - - /// JSON 역직렬화 - factory SessionStatistics.fromJson(Map json) { - return SessionStatistics( - playTimeMs: json['playTimeMs'] as int? ?? 0, - monstersKilled: json['monstersKilled'] as int? ?? 0, - goldEarned: json['goldEarned'] as int? ?? 0, - goldSpent: json['goldSpent'] as int? ?? 0, - skillsUsed: json['skillsUsed'] as int? ?? 0, - criticalHits: json['criticalHits'] as int? ?? 0, - maxCriticalStreak: json['maxCriticalStreak'] as int? ?? 0, - currentCriticalStreak: 0, // 세션간 유지 안 함 - totalDamageDealt: json['totalDamageDealt'] as int? ?? 0, - totalDamageTaken: json['totalDamageTaken'] as int? ?? 0, - potionsUsed: json['potionsUsed'] as int? ?? 0, - itemsSold: json['itemsSold'] as int? ?? 0, - questsCompleted: json['questsCompleted'] as int? ?? 0, - deathCount: json['deathCount'] as int? ?? 0, - bossesDefeated: json['bossesDefeated'] as int? ?? 0, - levelUps: json['levelUps'] as int? ?? 0, - ); - } -} - -/// 누적 통계 (Cumulative Statistics) -/// -/// 모든 게임 세션의 누적 통계 -class CumulativeStatistics { - const CumulativeStatistics({ - required this.totalPlayTimeMs, - required this.totalMonstersKilled, - required this.totalGoldEarned, - required this.totalGoldSpent, - required this.totalSkillsUsed, - required this.totalCriticalHits, - required this.bestCriticalStreak, - required this.totalDamageDealt, - required this.totalDamageTaken, - required this.totalPotionsUsed, - required this.totalItemsSold, - required this.totalQuestsCompleted, - required this.totalDeaths, - required this.totalBossesDefeated, - required this.totalLevelUps, - required this.highestLevel, - required this.highestGoldHeld, - required this.gamesCompleted, - required this.gamesStarted, - }); - - /// 총 플레이 시간 (밀리초) - final int totalPlayTimeMs; - - /// 총 처치한 몬스터 수 - final int totalMonstersKilled; - - /// 총 획득한 골드 - final int totalGoldEarned; - - /// 총 소비한 골드 - final int totalGoldSpent; - - /// 총 스킬 사용 횟수 - final int totalSkillsUsed; - - /// 총 크리티컬 히트 횟수 - final int totalCriticalHits; - - /// 최고 연속 크리티컬 - final int bestCriticalStreak; - - /// 총 입힌 데미지 - final int totalDamageDealt; - - /// 총 받은 데미지 - final int totalDamageTaken; - - /// 총 사용한 물약 수 - final int totalPotionsUsed; - - /// 총 판매한 아이템 수 - final int totalItemsSold; - - /// 총 완료한 퀘스트 수 - final int totalQuestsCompleted; - - /// 총 사망 횟수 - final int totalDeaths; - - /// 총 처치한 보스 수 - final int totalBossesDefeated; - - /// 총 레벨업 횟수 - final int totalLevelUps; - - /// 최고 달성 레벨 - final int highestLevel; - - /// 최대 보유 골드 - final int highestGoldHeld; - - /// 클리어한 게임 수 - final int gamesCompleted; - - /// 시작한 게임 수 - final int gamesStarted; - - /// 총 플레이 시간 Duration - Duration get totalPlayTime => Duration(milliseconds: totalPlayTimeMs); - - /// 총 플레이 시간 포맷 (HH:MM:SS) - String get formattedTotalPlayTime { - final hours = totalPlayTime.inHours; - final minutes = totalPlayTime.inMinutes % 60; - final seconds = totalPlayTime.inSeconds % 60; - return '${hours.toString().padLeft(2, '0')}:' - '${minutes.toString().padLeft(2, '0')}:' - '${seconds.toString().padLeft(2, '0')}'; - } - - /// 평균 게임당 플레이 시간 - Duration get averagePlayTimePerGame { - if (gamesStarted <= 0) return Duration.zero; - return Duration(milliseconds: totalPlayTimeMs ~/ gamesStarted); - } - - /// 게임 완료율 - double get completionRate { - if (gamesStarted <= 0) return 0; - return gamesCompleted / gamesStarted; - } - - /// 빈 누적 통계 - factory CumulativeStatistics.empty() => const CumulativeStatistics( - totalPlayTimeMs: 0, - totalMonstersKilled: 0, - totalGoldEarned: 0, - totalGoldSpent: 0, - totalSkillsUsed: 0, - totalCriticalHits: 0, - bestCriticalStreak: 0, - totalDamageDealt: 0, - totalDamageTaken: 0, - totalPotionsUsed: 0, - totalItemsSold: 0, - totalQuestsCompleted: 0, - totalDeaths: 0, - totalBossesDefeated: 0, - totalLevelUps: 0, - highestLevel: 0, - highestGoldHeld: 0, - gamesCompleted: 0, - gamesStarted: 0, - ); - - /// 세션 통계 병합 - CumulativeStatistics mergeSession(SessionStatistics session) { - return CumulativeStatistics( - totalPlayTimeMs: totalPlayTimeMs + session.playTimeMs, - totalMonstersKilled: totalMonstersKilled + session.monstersKilled, - totalGoldEarned: totalGoldEarned + session.goldEarned, - totalGoldSpent: totalGoldSpent + session.goldSpent, - totalSkillsUsed: totalSkillsUsed + session.skillsUsed, - totalCriticalHits: totalCriticalHits + session.criticalHits, - bestCriticalStreak: session.maxCriticalStreak > bestCriticalStreak - ? session.maxCriticalStreak - : bestCriticalStreak, - totalDamageDealt: totalDamageDealt + session.totalDamageDealt, - totalDamageTaken: totalDamageTaken + session.totalDamageTaken, - totalPotionsUsed: totalPotionsUsed + session.potionsUsed, - totalItemsSold: totalItemsSold + session.itemsSold, - totalQuestsCompleted: totalQuestsCompleted + session.questsCompleted, - totalDeaths: totalDeaths + session.deathCount, - totalBossesDefeated: totalBossesDefeated + session.bossesDefeated, - totalLevelUps: totalLevelUps + session.levelUps, - highestLevel: highestLevel, // 별도 업데이트 필요 - highestGoldHeld: highestGoldHeld, // 별도 업데이트 필요 - gamesCompleted: gamesCompleted, // 별도 업데이트 필요 - gamesStarted: gamesStarted, // 별도 업데이트 필요 - ); - } - - /// 최고 레벨 업데이트 - CumulativeStatistics updateHighestLevel(int level) { - if (level <= highestLevel) return this; - return copyWith(highestLevel: level); - } - - /// 최대 골드 업데이트 - CumulativeStatistics updateHighestGold(int gold) { - if (gold <= highestGoldHeld) return this; - return copyWith(highestGoldHeld: gold); - } - - /// 새 게임 시작 기록 - CumulativeStatistics recordGameStart() { - return copyWith(gamesStarted: gamesStarted + 1); - } - - /// 게임 클리어 기록 - CumulativeStatistics recordGameComplete() { - return copyWith(gamesCompleted: gamesCompleted + 1); - } - - CumulativeStatistics copyWith({ - int? totalPlayTimeMs, - int? totalMonstersKilled, - int? totalGoldEarned, - int? totalGoldSpent, - int? totalSkillsUsed, - int? totalCriticalHits, - int? bestCriticalStreak, - int? totalDamageDealt, - int? totalDamageTaken, - int? totalPotionsUsed, - int? totalItemsSold, - int? totalQuestsCompleted, - int? totalDeaths, - int? totalBossesDefeated, - int? totalLevelUps, - int? highestLevel, - int? highestGoldHeld, - int? gamesCompleted, - int? gamesStarted, - }) { - return CumulativeStatistics( - totalPlayTimeMs: totalPlayTimeMs ?? this.totalPlayTimeMs, - totalMonstersKilled: totalMonstersKilled ?? this.totalMonstersKilled, - totalGoldEarned: totalGoldEarned ?? this.totalGoldEarned, - totalGoldSpent: totalGoldSpent ?? this.totalGoldSpent, - totalSkillsUsed: totalSkillsUsed ?? this.totalSkillsUsed, - totalCriticalHits: totalCriticalHits ?? this.totalCriticalHits, - bestCriticalStreak: bestCriticalStreak ?? this.bestCriticalStreak, - totalDamageDealt: totalDamageDealt ?? this.totalDamageDealt, - totalDamageTaken: totalDamageTaken ?? this.totalDamageTaken, - totalPotionsUsed: totalPotionsUsed ?? this.totalPotionsUsed, - totalItemsSold: totalItemsSold ?? this.totalItemsSold, - totalQuestsCompleted: totalQuestsCompleted ?? this.totalQuestsCompleted, - totalDeaths: totalDeaths ?? this.totalDeaths, - totalBossesDefeated: totalBossesDefeated ?? this.totalBossesDefeated, - totalLevelUps: totalLevelUps ?? this.totalLevelUps, - highestLevel: highestLevel ?? this.highestLevel, - highestGoldHeld: highestGoldHeld ?? this.highestGoldHeld, - gamesCompleted: gamesCompleted ?? this.gamesCompleted, - gamesStarted: gamesStarted ?? this.gamesStarted, - ); - } - - /// JSON 직렬화 - Map toJson() { - return { - 'totalPlayTimeMs': totalPlayTimeMs, - 'totalMonstersKilled': totalMonstersKilled, - 'totalGoldEarned': totalGoldEarned, - 'totalGoldSpent': totalGoldSpent, - 'totalSkillsUsed': totalSkillsUsed, - 'totalCriticalHits': totalCriticalHits, - 'bestCriticalStreak': bestCriticalStreak, - 'totalDamageDealt': totalDamageDealt, - 'totalDamageTaken': totalDamageTaken, - 'totalPotionsUsed': totalPotionsUsed, - 'totalItemsSold': totalItemsSold, - 'totalQuestsCompleted': totalQuestsCompleted, - 'totalDeaths': totalDeaths, - 'totalBossesDefeated': totalBossesDefeated, - 'totalLevelUps': totalLevelUps, - 'highestLevel': highestLevel, - 'highestGoldHeld': highestGoldHeld, - 'gamesCompleted': gamesCompleted, - 'gamesStarted': gamesStarted, - }; - } - - /// JSON 역직렬화 - factory CumulativeStatistics.fromJson(Map json) { - return CumulativeStatistics( - totalPlayTimeMs: json['totalPlayTimeMs'] as int? ?? 0, - totalMonstersKilled: json['totalMonstersKilled'] as int? ?? 0, - totalGoldEarned: json['totalGoldEarned'] as int? ?? 0, - totalGoldSpent: json['totalGoldSpent'] as int? ?? 0, - totalSkillsUsed: json['totalSkillsUsed'] as int? ?? 0, - totalCriticalHits: json['totalCriticalHits'] as int? ?? 0, - bestCriticalStreak: json['bestCriticalStreak'] as int? ?? 0, - totalDamageDealt: json['totalDamageDealt'] as int? ?? 0, - totalDamageTaken: json['totalDamageTaken'] as int? ?? 0, - totalPotionsUsed: json['totalPotionsUsed'] as int? ?? 0, - totalItemsSold: json['totalItemsSold'] as int? ?? 0, - totalQuestsCompleted: json['totalQuestsCompleted'] as int? ?? 0, - totalDeaths: json['totalDeaths'] as int? ?? 0, - totalBossesDefeated: json['totalBossesDefeated'] as int? ?? 0, - totalLevelUps: json['totalLevelUps'] as int? ?? 0, - highestLevel: json['highestLevel'] as int? ?? 0, - highestGoldHeld: json['highestGoldHeld'] as int? ?? 0, - gamesCompleted: json['gamesCompleted'] as int? ?? 0, - gamesStarted: json['gamesStarted'] as int? ?? 0, - ); - } -} diff --git a/lib/src/core/model/session_statistics.dart b/lib/src/core/model/session_statistics.dart new file mode 100644 index 0000000..26995a8 --- /dev/null +++ b/lib/src/core/model/session_statistics.dart @@ -0,0 +1,279 @@ +/// 세션 통계 (Session Statistics) +/// +/// GameStatistics에서 분리된 현재 게임 세션의 통계 모델. +class SessionStatistics { + const SessionStatistics({ + required this.playTimeMs, + required this.monstersKilled, + required this.goldEarned, + required this.goldSpent, + required this.skillsUsed, + required this.criticalHits, + required this.maxCriticalStreak, + required this.currentCriticalStreak, + required this.totalDamageDealt, + required this.totalDamageTaken, + required this.potionsUsed, + required this.itemsSold, + required this.questsCompleted, + required this.deathCount, + required this.bossesDefeated, + required this.levelUps, + }); + + /// 플레이 시간 (밀리초) + final int playTimeMs; + + /// 처치한 몬스터 수 + final int monstersKilled; + + /// 획득한 골드 총량 + final int goldEarned; + + /// 소비한 골드 총량 + final int goldSpent; + + /// 사용한 스킬 횟수 + final int skillsUsed; + + /// 크리티컬 히트 횟수 + final int criticalHits; + + /// 최대 연속 크리티컬 + final int maxCriticalStreak; + + /// 현재 연속 크리티컬 (내부 추적용) + final int currentCriticalStreak; + + /// 총 입힌 데미지 + final int totalDamageDealt; + + /// 총 받은 데미지 + final int totalDamageTaken; + + /// 사용한 물약 수 + final int potionsUsed; + + /// 판매한 아이템 수 + final int itemsSold; + + /// 완료한 퀘스트 수 + final int questsCompleted; + + /// 사망 횟수 + final int deathCount; + + /// 처치한 보스 수 + final int bossesDefeated; + + /// 레벨업 횟수 + final int levelUps; + + /// 플레이 시간 Duration + Duration get playTime => Duration(milliseconds: playTimeMs); + + /// 플레이 시간 포맷 (HH:MM:SS) + String get formattedPlayTime { + final hours = playTime.inHours; + final minutes = playTime.inMinutes % 60; + final seconds = playTime.inSeconds % 60; + return '${hours.toString().padLeft(2, '0')}:' + '${minutes.toString().padLeft(2, '0')}:' + '${seconds.toString().padLeft(2, '0')}'; + } + + /// 평균 DPS (damage per second) + double get averageDps { + if (playTimeMs <= 0) return 0; + return totalDamageDealt / (playTimeMs / 1000); + } + + /// 킬당 평균 골드 + double get goldPerKill { + if (monstersKilled <= 0) return 0; + return goldEarned / monstersKilled; + } + + /// 크리티컬 비율 + double get criticalRate { + if (skillsUsed <= 0) return 0; + return criticalHits / skillsUsed; + } + + /// 빈 세션 통계 + factory SessionStatistics.empty() => const SessionStatistics( + playTimeMs: 0, + monstersKilled: 0, + goldEarned: 0, + goldSpent: 0, + skillsUsed: 0, + criticalHits: 0, + maxCriticalStreak: 0, + currentCriticalStreak: 0, + totalDamageDealt: 0, + totalDamageTaken: 0, + potionsUsed: 0, + itemsSold: 0, + questsCompleted: 0, + deathCount: 0, + bossesDefeated: 0, + levelUps: 0, + ); + + // ============================================================================ + // 이벤트 기록 메서드 + // ============================================================================ + + /// 몬스터 처치 기록 + SessionStatistics recordKill({bool isBoss = false}) { + return copyWith( + monstersKilled: monstersKilled + 1, + bossesDefeated: isBoss ? bossesDefeated + 1 : bossesDefeated, + ); + } + + /// 골드 획득 기록 + SessionStatistics recordGoldEarned(int amount) { + return copyWith(goldEarned: goldEarned + amount); + } + + /// 골드 소비 기록 + SessionStatistics recordGoldSpent(int amount) { + return copyWith(goldSpent: goldSpent + amount); + } + + /// 스킬 사용 기록 + SessionStatistics recordSkillUse({required bool isCritical}) { + final newCriticalStreak = isCritical ? currentCriticalStreak + 1 : 0; + final newMaxStreak = newCriticalStreak > maxCriticalStreak + ? newCriticalStreak + : maxCriticalStreak; + + return copyWith( + skillsUsed: skillsUsed + 1, + criticalHits: isCritical ? criticalHits + 1 : criticalHits, + currentCriticalStreak: newCriticalStreak, + maxCriticalStreak: newMaxStreak, + ); + } + + /// 데미지 기록 + SessionStatistics recordDamage({int dealt = 0, int taken = 0}) { + return copyWith( + totalDamageDealt: totalDamageDealt + dealt, + totalDamageTaken: totalDamageTaken + taken, + ); + } + + /// 물약 사용 기록 + SessionStatistics recordPotionUse() { + return copyWith(potionsUsed: potionsUsed + 1); + } + + /// 아이템 판매 기록 + SessionStatistics recordItemSold(int count) { + return copyWith(itemsSold: itemsSold + count); + } + + /// 퀘스트 완료 기록 + SessionStatistics recordQuestComplete() { + return copyWith(questsCompleted: questsCompleted + 1); + } + + /// 사망 기록 + SessionStatistics recordDeath() { + return copyWith(deathCount: deathCount + 1); + } + + /// 레벨업 기록 + SessionStatistics recordLevelUp() { + return copyWith(levelUps: levelUps + 1); + } + + /// 플레이 시간 업데이트 + SessionStatistics updatePlayTime(int elapsedMs) { + return copyWith(playTimeMs: elapsedMs); + } + + SessionStatistics copyWith({ + int? playTimeMs, + int? monstersKilled, + int? goldEarned, + int? goldSpent, + int? skillsUsed, + int? criticalHits, + int? maxCriticalStreak, + int? currentCriticalStreak, + int? totalDamageDealt, + int? totalDamageTaken, + int? potionsUsed, + int? itemsSold, + int? questsCompleted, + int? deathCount, + int? bossesDefeated, + int? levelUps, + }) { + return SessionStatistics( + playTimeMs: playTimeMs ?? this.playTimeMs, + monstersKilled: monstersKilled ?? this.monstersKilled, + goldEarned: goldEarned ?? this.goldEarned, + goldSpent: goldSpent ?? this.goldSpent, + skillsUsed: skillsUsed ?? this.skillsUsed, + criticalHits: criticalHits ?? this.criticalHits, + maxCriticalStreak: maxCriticalStreak ?? this.maxCriticalStreak, + currentCriticalStreak: + currentCriticalStreak ?? this.currentCriticalStreak, + totalDamageDealt: totalDamageDealt ?? this.totalDamageDealt, + totalDamageTaken: totalDamageTaken ?? this.totalDamageTaken, + potionsUsed: potionsUsed ?? this.potionsUsed, + itemsSold: itemsSold ?? this.itemsSold, + questsCompleted: questsCompleted ?? this.questsCompleted, + deathCount: deathCount ?? this.deathCount, + bossesDefeated: bossesDefeated ?? this.bossesDefeated, + levelUps: levelUps ?? this.levelUps, + ); + } + + /// JSON 직렬화 + Map toJson() { + return { + 'playTimeMs': playTimeMs, + 'monstersKilled': monstersKilled, + 'goldEarned': goldEarned, + 'goldSpent': goldSpent, + 'skillsUsed': skillsUsed, + 'criticalHits': criticalHits, + 'maxCriticalStreak': maxCriticalStreak, + 'totalDamageDealt': totalDamageDealt, + 'totalDamageTaken': totalDamageTaken, + 'potionsUsed': potionsUsed, + 'itemsSold': itemsSold, + 'questsCompleted': questsCompleted, + 'deathCount': deathCount, + 'bossesDefeated': bossesDefeated, + 'levelUps': levelUps, + }; + } + + /// JSON 역직렬화 + factory SessionStatistics.fromJson(Map json) { + return SessionStatistics( + playTimeMs: json['playTimeMs'] as int? ?? 0, + monstersKilled: json['monstersKilled'] as int? ?? 0, + goldEarned: json['goldEarned'] as int? ?? 0, + goldSpent: json['goldSpent'] as int? ?? 0, + skillsUsed: json['skillsUsed'] as int? ?? 0, + criticalHits: json['criticalHits'] as int? ?? 0, + maxCriticalStreak: json['maxCriticalStreak'] as int? ?? 0, + currentCriticalStreak: 0, // 세션간 유지 안 함 + totalDamageDealt: json['totalDamageDealt'] as int? ?? 0, + totalDamageTaken: json['totalDamageTaken'] as int? ?? 0, + potionsUsed: json['potionsUsed'] as int? ?? 0, + itemsSold: json['itemsSold'] as int? ?? 0, + questsCompleted: json['questsCompleted'] as int? ?? 0, + deathCount: json['deathCount'] as int? ?? 0, + bossesDefeated: json['bossesDefeated'] as int? ?? 0, + levelUps: json['levelUps'] as int? ?? 0, + ); + } +} diff --git a/lib/src/core/model/task_info.dart b/lib/src/core/model/task_info.dart index 089a2a6..dc5b936 100644 --- a/lib/src/core/model/task_info.dart +++ b/lib/src/core/model/task_info.dart @@ -1,4 +1,4 @@ -import 'package:asciineverdie/src/core/animation/monster_size.dart'; +import 'package:asciineverdie/src/shared/animation/monster_size.dart'; import 'package:asciineverdie/src/core/model/monster_grade.dart'; /// 태스크 타입 (원본 fTask.Caption 값들에 대응)