feat(ui): 도움말 다이얼로그 및 UI 개선

- HelpDialog 추가
- 게임 화면에 통계/도움말 버튼 추가
- CombatLog에 디버프 이벤트 표시
- AudioService mp3 확장자 지원
- 설정 텍스트 l10n 추가
This commit is contained in:
JiWoong Sul
2025-12-30 15:58:40 +09:00
parent d64b9654a3
commit 18af93824b
10 changed files with 1028 additions and 32 deletions

View File

@@ -5,7 +5,9 @@ import 'package:askiineverdie/src/core/engine/progress_service.dart';
import 'package:askiineverdie/src/core/engine/resurrection_service.dart';
import 'package:askiineverdie/src/core/engine/shop_service.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
import 'package:askiineverdie/src/core/model/game_statistics.dart';
import 'package:askiineverdie/src/core/storage/save_manager.dart';
import 'package:askiineverdie/src/core/storage/statistics_storage.dart';
import 'package:flutter/foundation.dart';
enum GameSessionStatus { idle, loading, running, error, dead }
@@ -18,12 +20,15 @@ class GameSessionController extends ChangeNotifier {
this.autoSaveConfig = const AutoSaveConfig(),
Duration tickInterval = const Duration(milliseconds: 50),
DateTime Function()? now,
StatisticsStorage? statisticsStorage,
}) : _tickInterval = tickInterval,
_now = now ?? DateTime.now;
_now = now ?? DateTime.now,
_statisticsStorage = statisticsStorage ?? StatisticsStorage();
final ProgressService progressService;
final SaveManager saveManager;
final AutoSaveConfig autoSaveConfig;
final StatisticsStorage _statisticsStorage;
final Duration _tickInterval;
final DateTime Function() _now;
@@ -36,12 +41,26 @@ class GameSessionController extends ChangeNotifier {
GameState? _state;
String? _error;
// 통계 관련 필드
SessionStatistics _sessionStats = SessionStatistics.empty();
CumulativeStatistics _cumulativeStats = CumulativeStatistics.empty();
int _previousLevel = 0;
int _previousGold = 0;
int _previousMonstersKilled = 0;
int _previousQuestsCompleted = 0;
GameSessionStatus get status => _status;
GameState? get state => _state;
String? get error => _error;
bool get isRunning => _status == GameSessionStatus.running;
bool get cheatsEnabled => _cheatsEnabled;
/// 현재 세션 통계
SessionStatistics get sessionStats => _sessionStats;
/// 누적 통계
CumulativeStatistics get cumulativeStats => _cumulativeStats;
/// 현재 ProgressLoop 인스턴스 (치트 기능용)
ProgressLoop? get loop => _loop;
@@ -62,6 +81,13 @@ class GameSessionController extends ChangeNotifier {
_status = GameSessionStatus.running;
_cheatsEnabled = cheatsEnabled;
// 통계 초기화
if (isNewGame) {
_sessionStats = SessionStatistics.empty();
await _statisticsStorage.recordGameStart();
}
_initPreviousValues(state);
_loop = ProgressLoop(
initialState: state,
progressService: progressService,
@@ -74,6 +100,7 @@ class GameSessionController extends ChangeNotifier {
);
_subscription = _loop!.stream.listen((next) {
_updateStatistics(next);
_state = next;
notifyListeners();
});
@@ -82,6 +109,76 @@ class GameSessionController extends ChangeNotifier {
notifyListeners();
}
/// 이전 값 초기화 (통계 변화 추적용)
void _initPreviousValues(GameState state) {
_previousLevel = state.traits.level;
_previousGold = state.inventory.gold;
_previousMonstersKilled = state.progress.monstersKilled;
_previousQuestsCompleted = state.progress.questCount;
}
/// 상태 변화에 따른 통계 업데이트
void _updateStatistics(GameState next) {
// 플레이 시간 업데이트
_sessionStats = _sessionStats.updatePlayTime(next.skillSystem.elapsedMs);
// 레벨업 감지
if (next.traits.level > _previousLevel) {
final levelUps = next.traits.level - _previousLevel;
for (var i = 0; i < levelUps; i++) {
_sessionStats = _sessionStats.recordLevelUp();
}
_previousLevel = next.traits.level;
// 최고 레벨 업데이트
unawaited(_statisticsStorage.updateHighestLevel(next.traits.level));
}
// 골드 변화 감지
if (next.inventory.gold > _previousGold) {
final earned = next.inventory.gold - _previousGold;
_sessionStats = _sessionStats.recordGoldEarned(earned);
// 최대 골드 업데이트
unawaited(_statisticsStorage.updateHighestGold(next.inventory.gold));
} else if (next.inventory.gold < _previousGold) {
final spent = _previousGold - next.inventory.gold;
_sessionStats = _sessionStats.recordGoldSpent(spent);
}
_previousGold = next.inventory.gold;
// 몬스터 처치 감지
if (next.progress.monstersKilled > _previousMonstersKilled) {
final kills = next.progress.monstersKilled - _previousMonstersKilled;
for (var i = 0; i < kills; i++) {
_sessionStats = _sessionStats.recordKill();
}
_previousMonstersKilled = next.progress.monstersKilled;
}
// 퀘스트 완료 감지
if (next.progress.questCount > _previousQuestsCompleted) {
final quests = next.progress.questCount - _previousQuestsCompleted;
for (var i = 0; i < quests; i++) {
_sessionStats = _sessionStats.recordQuestComplete();
}
_previousQuestsCompleted = next.progress.questCount;
}
}
/// 누적 통계 로드
Future<void> loadCumulativeStats() async {
_cumulativeStats = await _statisticsStorage.loadCumulative();
notifyListeners();
}
/// 세션 통계를 누적 통계에 병합
Future<void> mergeSessionStats() async {
await _statisticsStorage.mergeSession(_sessionStats);
_cumulativeStats = await _statisticsStorage.loadCumulative();
notifyListeners();
}
Future<void> loadAndStart({
String? fileName,
bool cheatsEnabled = false,
@@ -148,6 +245,7 @@ class GameSessionController extends ChangeNotifier {
/// 플레이어 사망 콜백 (ProgressLoop에서 호출)
void _onPlayerDied() {
_sessionStats = _sessionStats.recordDeath();
_status = GameSessionStatus.dead;
notifyListeners();
}