feat(game): 게임 세션 및 사망 오버레이 개선
- GameSessionController 기능 확장 - DeathOverlay 상세 사망 정보 표시 - GamePlayScreen 연동 업데이트
This commit is contained in:
@@ -256,9 +256,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
final taskType = state.progress.currentTask.type;
|
final taskType = state.progress.currentTask.type;
|
||||||
final isInBattleTask = taskType == TaskType.kill;
|
final isInBattleTask = taskType == TaskType.kill;
|
||||||
|
|
||||||
debugPrint(
|
|
||||||
'[BGM] TaskType: $taskType, isInBattle: $isInBattleTask, currentBgm: ${audio.currentBgm}');
|
|
||||||
|
|
||||||
// 전투 태스크 상태 결정
|
// 전투 태스크 상태 결정
|
||||||
if (isInBattleTask) {
|
if (isInBattleTask) {
|
||||||
// 전투 태스크: 보스 여부에 따라 BGM 선택
|
// 전투 태스크: 보스 여부에 따라 BGM 선택
|
||||||
@@ -269,13 +266,11 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
|
|
||||||
// 전환 시점이거나 현재 BGM이 일치하지 않으면 재생
|
// 전환 시점이거나 현재 BGM이 일치하지 않으면 재생
|
||||||
if (!_wasInBattleTask || audio.currentBgm != expectedBgm) {
|
if (!_wasInBattleTask || audio.currentBgm != expectedBgm) {
|
||||||
debugPrint('[BGM] Playing battle BGM: $expectedBgm');
|
|
||||||
audio.playBgm(expectedBgm);
|
audio.playBgm(expectedBgm);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 비전투 태스크: 항상 마을 BGM 유지 (이미 town이면 스킵)
|
// 비전투 태스크: 항상 마을 BGM 유지 (이미 town이면 스킵)
|
||||||
if (audio.currentBgm != 'town') {
|
if (audio.currentBgm != 'town') {
|
||||||
debugPrint('[BGM] Playing town BGM (was: ${audio.currentBgm})');
|
|
||||||
audio.playBgm('town');
|
audio.playBgm('town');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -825,6 +820,12 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
deathInfo: state.deathInfo!,
|
deathInfo: state.deathInfo!,
|
||||||
traits: state.traits,
|
traits: state.traits,
|
||||||
onResurrect: _handleResurrect,
|
onResurrect: _handleResurrect,
|
||||||
|
isAutoResurrectEnabled: widget.controller.autoResurrect,
|
||||||
|
onToggleAutoResurrect: () {
|
||||||
|
widget.controller.setAutoResurrect(
|
||||||
|
!widget.controller.autoResurrect,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
// 승리 오버레이 (게임 클리어)
|
// 승리 오버레이 (게임 클리어)
|
||||||
if (widget.controller.isComplete)
|
if (widget.controller.isComplete)
|
||||||
@@ -968,6 +969,12 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
deathInfo: state.deathInfo!,
|
deathInfo: state.deathInfo!,
|
||||||
traits: state.traits,
|
traits: state.traits,
|
||||||
onResurrect: _handleResurrect,
|
onResurrect: _handleResurrect,
|
||||||
|
isAutoResurrectEnabled: widget.controller.autoResurrect,
|
||||||
|
onToggleAutoResurrect: () {
|
||||||
|
widget.controller.setAutoResurrect(
|
||||||
|
!widget.controller.autoResurrect,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
// 승리 오버레이 (게임 클리어)
|
// 승리 오버레이 (게임 클리어)
|
||||||
if (widget.controller.isComplete)
|
if (widget.controller.isComplete)
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
GameState? _state;
|
GameState? _state;
|
||||||
String? _error;
|
String? _error;
|
||||||
|
|
||||||
|
// 자동 부활 (Auto-Resurrection) 상태
|
||||||
|
bool _autoResurrect = false;
|
||||||
|
|
||||||
// 통계 관련 필드
|
// 통계 관련 필드
|
||||||
SessionStatistics _sessionStats = SessionStatistics.empty();
|
SessionStatistics _sessionStats = SessionStatistics.empty();
|
||||||
CumulativeStatistics _cumulativeStats = CumulativeStatistics.empty();
|
CumulativeStatistics _cumulativeStats = CumulativeStatistics.empty();
|
||||||
@@ -61,6 +64,15 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
bool get isRunning => _status == GameSessionStatus.running;
|
bool get isRunning => _status == GameSessionStatus.running;
|
||||||
bool get cheatsEnabled => _cheatsEnabled;
|
bool get cheatsEnabled => _cheatsEnabled;
|
||||||
|
|
||||||
|
/// 자동 부활 활성화 여부
|
||||||
|
bool get autoResurrect => _autoResurrect;
|
||||||
|
|
||||||
|
/// 자동 부활 설정
|
||||||
|
void setAutoResurrect(bool value) {
|
||||||
|
_autoResurrect = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
/// 현재 세션 통계
|
/// 현재 세션 통계
|
||||||
SessionStatistics get sessionStats => _sessionStats;
|
SessionStatistics get sessionStats => _sessionStats;
|
||||||
|
|
||||||
@@ -75,6 +87,9 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
bool cheatsEnabled = false,
|
bool cheatsEnabled = false,
|
||||||
bool isNewGame = true,
|
bool isNewGame = true,
|
||||||
}) async {
|
}) async {
|
||||||
|
// 기존 배속 보존 (부활/재개 시 유지)
|
||||||
|
final previousSpeed = _loop?.speedMultiplier ?? 1;
|
||||||
|
|
||||||
await _stopLoop(saveOnStop: false);
|
await _stopLoop(saveOnStop: false);
|
||||||
|
|
||||||
// 새 게임인 경우 초기화 (프롤로그 태스크 설정)
|
// 새 게임인 경우 초기화 (프롤로그 태스크 설정)
|
||||||
@@ -97,6 +112,9 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
// 명예의 전당 체크 → 가용 배속 결정
|
// 명예의 전당 체크 → 가용 배속 결정
|
||||||
final availableSpeeds = await _getAvailableSpeeds();
|
final availableSpeeds = await _getAvailableSpeeds();
|
||||||
|
|
||||||
|
// 새 게임이면 1배속, 재개/부활이면 기존 배속 유지
|
||||||
|
final initialSpeed = isNewGame ? 1 : previousSpeed;
|
||||||
|
|
||||||
_loop = ProgressLoop(
|
_loop = ProgressLoop(
|
||||||
initialState: state,
|
initialState: state,
|
||||||
progressService: progressService,
|
progressService: progressService,
|
||||||
@@ -108,6 +126,7 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
onPlayerDied: _onPlayerDied,
|
onPlayerDied: _onPlayerDied,
|
||||||
onGameComplete: _onGameComplete,
|
onGameComplete: _onGameComplete,
|
||||||
availableSpeeds: availableSpeeds,
|
availableSpeeds: availableSpeeds,
|
||||||
|
initialSpeedMultiplier: initialSpeed,
|
||||||
);
|
);
|
||||||
|
|
||||||
_subscription = _loop!.stream.listen((next) {
|
_subscription = _loop!.stream.listen((next) {
|
||||||
@@ -273,6 +292,24 @@ class GameSessionController extends ChangeNotifier {
|
|||||||
_sessionStats = _sessionStats.recordDeath();
|
_sessionStats = _sessionStats.recordDeath();
|
||||||
_status = GameSessionStatus.dead;
|
_status = GameSessionStatus.dead;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
|
// 자동 부활이 활성화된 경우 잠시 후 자동으로 부활
|
||||||
|
if (_autoResurrect) {
|
||||||
|
_scheduleAutoResurrect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 자동 부활 예약 (Auto-Resurrection Scheduler)
|
||||||
|
///
|
||||||
|
/// 사망 오버레이를 잠시 표시한 후 자동으로 부활 처리
|
||||||
|
void _scheduleAutoResurrect() {
|
||||||
|
Future.delayed(const Duration(milliseconds: 800), () async {
|
||||||
|
// 상태가 여전히 dead이고, 자동 부활이 활성화된 경우에만 부활
|
||||||
|
if (_status == GameSessionStatus.dead && _autoResurrect) {
|
||||||
|
await resurrect();
|
||||||
|
await resumeAfterResurrection();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 게임 클리어 콜백 (ProgressLoop에서 호출, Act V 완료 시)
|
/// 게임 클리어 콜백 (ProgressLoop에서 호출, Act V 완료 시)
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class DeathOverlay extends StatelessWidget {
|
|||||||
required this.deathInfo,
|
required this.deathInfo,
|
||||||
required this.traits,
|
required this.traits,
|
||||||
required this.onResurrect,
|
required this.onResurrect,
|
||||||
|
this.isAutoResurrectEnabled = false,
|
||||||
|
this.onToggleAutoResurrect,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 사망 정보
|
/// 사망 정보
|
||||||
@@ -26,6 +28,12 @@ class DeathOverlay extends StatelessWidget {
|
|||||||
/// 부활 버튼 콜백
|
/// 부활 버튼 콜백
|
||||||
final VoidCallback onResurrect;
|
final VoidCallback onResurrect;
|
||||||
|
|
||||||
|
/// 자동 부활 활성화 여부
|
||||||
|
final bool isAutoResurrectEnabled;
|
||||||
|
|
||||||
|
/// 자동 부활 토글 콜백
|
||||||
|
final VoidCallback? onToggleAutoResurrect;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// 테마 인식 색상 (Theme-aware colors)
|
// 테마 인식 색상 (Theme-aware colors)
|
||||||
@@ -132,6 +140,12 @@ class DeathOverlay extends StatelessWidget {
|
|||||||
|
|
||||||
// 부활 버튼
|
// 부활 버튼
|
||||||
_buildResurrectButton(context),
|
_buildResurrectButton(context),
|
||||||
|
|
||||||
|
// 자동 부활 버튼
|
||||||
|
if (onToggleAutoResurrect != null) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_buildAutoResurrectButton(context),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -456,6 +470,75 @@ class DeathOverlay extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 자동 부활 토글 버튼
|
||||||
|
Widget _buildAutoResurrectButton(BuildContext context) {
|
||||||
|
final mpColor = RetroColors.mpOf(context);
|
||||||
|
final mpDark = RetroColors.mpDarkOf(context);
|
||||||
|
final muted = RetroColors.textMutedOf(context);
|
||||||
|
|
||||||
|
// 활성화 상태에 따른 색상
|
||||||
|
final buttonColor = isAutoResurrectEnabled ? mpColor : muted;
|
||||||
|
final buttonDark = isAutoResurrectEnabled ? mpDark : muted.withValues(alpha: 0.5);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onToggleAutoResurrect,
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: buttonColor.withValues(alpha: 0.15),
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(color: buttonColor, width: 2),
|
||||||
|
left: BorderSide(color: buttonColor, width: 2),
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: buttonDark.withValues(alpha: 0.8),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
right: BorderSide(
|
||||||
|
color: buttonDark.withValues(alpha: 0.8),
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
isAutoResurrectEnabled ? '◉' : '○',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: buttonColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
l10n.deathAutoResurrect.toUpperCase(),
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 8,
|
||||||
|
color: buttonColor,
|
||||||
|
letterSpacing: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (isAutoResurrectEnabled) ...[
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'ON',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 8,
|
||||||
|
color: mpColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// 사망 직전 전투 로그 표시
|
/// 사망 직전 전투 로그 표시
|
||||||
Widget _buildCombatLog(BuildContext context) {
|
Widget _buildCombatLog(BuildContext context) {
|
||||||
final events = deathInfo.lastCombatEvents;
|
final events = deathInfo.lastCombatEvents;
|
||||||
|
|||||||
Reference in New Issue
Block a user