feat(ui): 게임 화면 및 UI 컴포넌트 개선
- front_screen: 프론트 화면 UI 업데이트 - game_play_screen: 게임 플레이 화면 수정 - game_session_controller: 세션 관리 로직 개선 - mobile_carousel_layout: 모바일 캐러셀 레이아웃 개선 - enhanced_animation_panel: 애니메이션 패널 업데이트 - help_dialog: 도움말 다이얼로그 수정 - return_rewards_dialog: 복귀 보상 다이얼로그 개선 - new_character_screen: 새 캐릭터 화면 수정 - settings_screen: 설정 화면 업데이트
This commit is contained in:
@@ -14,6 +14,7 @@ import 'package:asciineverdie/src/core/model/game_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/game_statistics.dart';
|
||||
import 'package:asciineverdie/src/core/model/hall_of_fame.dart';
|
||||
import 'package:asciineverdie/src/core/model/monetization_state.dart';
|
||||
import 'package:asciineverdie/src/core/model/treasure_chest.dart';
|
||||
import 'package:asciineverdie/src/core/storage/hall_of_fame_storage.dart';
|
||||
import 'package:asciineverdie/src/core/storage/save_manager.dart';
|
||||
import 'package:asciineverdie/src/core/storage/statistics_storage.dart';
|
||||
@@ -64,14 +65,16 @@ class GameSessionController extends ChangeNotifier {
|
||||
Timer? _speedBoostTimer;
|
||||
int _speedBoostRemainingSeconds = 0;
|
||||
static const int _speedBoostDuration = 300; // 5분
|
||||
static const int _speedBoostMultiplier = 5; // 5x 속도
|
||||
|
||||
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
|
||||
int get _speedBoostMultiplier => (kDebugMode && _cheatsEnabled) ? 20 : 5;
|
||||
|
||||
// 복귀 보상 상태 (Phase 7)
|
||||
MonetizationState _monetization = MonetizationState.initial();
|
||||
ReturnReward? _pendingReturnReward;
|
||||
ReturnChestReward? _pendingReturnReward;
|
||||
|
||||
/// 복귀 보상 콜백 (UI에서 다이얼로그 표시용)
|
||||
void Function(ReturnReward reward)? onReturnRewardAvailable;
|
||||
void Function(ReturnChestReward reward)? onReturnRewardAvailable;
|
||||
|
||||
// 통계 관련 필드
|
||||
SessionStatistics _sessionStats = SessionStatistics.empty();
|
||||
@@ -105,6 +108,12 @@ class GameSessionController extends ChangeNotifier {
|
||||
/// 현재 ProgressLoop 인스턴스 (치트 기능용)
|
||||
ProgressLoop? get loop => _loop;
|
||||
|
||||
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
|
||||
int get adSpeedMultiplier => _speedBoostMultiplier;
|
||||
|
||||
/// 2x 배속 해금 여부 (명예의 전당에 캐릭터가 있으면 true)
|
||||
bool get has2xUnlocked => _loop?.availableSpeeds.contains(2) ?? false;
|
||||
|
||||
Future<void> startNew(
|
||||
GameState initialState, {
|
||||
bool cheatsEnabled = false,
|
||||
@@ -172,13 +181,16 @@ class GameSessionController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// 가용 배속 목록 반환
|
||||
/// - 디버그 모드(치트 활성화): [1, 2, 20] (터보 모드 포함)
|
||||
/// - 일반 모드: [1, 2] (5x는 광고 버프로만 활성화)
|
||||
///
|
||||
/// - 기본: [1] (1x만)
|
||||
/// - 명예의 전당에 캐릭터 있으면: [1, 2] (2x 해금)
|
||||
/// - 광고 배속(5x/20x)은 별도 버프로만 활성화
|
||||
Future<List<int>> _getAvailableSpeeds() async {
|
||||
if (_cheatsEnabled) {
|
||||
return [1, 2, 20];
|
||||
final hallOfFame = await _hallOfFameStorage.load();
|
||||
if (hallOfFame.entries.isNotEmpty) {
|
||||
return [1, 2]; // 명예의 전당 캐릭터 있으면 2x 해금
|
||||
}
|
||||
return [1, 2];
|
||||
return [1]; // 기본: 1x만
|
||||
}
|
||||
|
||||
/// 이전 값 초기화 (통계 변화 추적용)
|
||||
@@ -700,7 +712,7 @@ class GameSessionController extends ChangeNotifier {
|
||||
MonetizationState get monetization => _monetization;
|
||||
|
||||
/// 대기 중인 복귀 보상
|
||||
ReturnReward? get pendingReturnReward => _pendingReturnReward;
|
||||
ReturnChestReward? get pendingReturnReward => _pendingReturnReward;
|
||||
|
||||
/// 복귀 보상 체크 (로드 시 호출)
|
||||
void _checkReturnRewards(GameState loaded) {
|
||||
@@ -715,17 +727,17 @@ class GameSessionController extends ChangeNotifier {
|
||||
final reward = rewardsService.calculateReward(
|
||||
lastPlayTime: lastPlayTime,
|
||||
currentTime: DateTime.now(),
|
||||
playerLevel: loaded.traits.level,
|
||||
isPaidUser: _monetization.isPaidUser,
|
||||
);
|
||||
|
||||
if (reward.hasReward) {
|
||||
_pendingReturnReward = reward;
|
||||
debugPrint('[ReturnRewards] Reward available: ${reward.goldReward} gold, '
|
||||
debugPrint('[ReturnRewards] Reward available: ${reward.chestCount} chests, '
|
||||
'${reward.hoursAway} hours away');
|
||||
|
||||
// UI에서 다이얼로그 표시를 위해 콜백 호출
|
||||
// startNew 후에 호출하도록 딜레이
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
Future<void>.delayed(const Duration(milliseconds: 500), () {
|
||||
if (_pendingReturnReward != null) {
|
||||
onReturnRewardAvailable?.call(_pendingReturnReward!);
|
||||
}
|
||||
@@ -733,23 +745,86 @@ class GameSessionController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// 복귀 보상 수령 완료 (골드 적용)
|
||||
/// 복귀 보상 수령 완료 (상자 보상 적용)
|
||||
///
|
||||
/// [totalGold] 수령한 총 골드 (기본 + 보너스)
|
||||
void applyReturnReward(int totalGold) {
|
||||
/// [rewards] 오픈된 상자 보상 목록
|
||||
void applyReturnReward(List<ChestReward> rewards) {
|
||||
if (_state == null) return;
|
||||
if (totalGold <= 0) {
|
||||
if (rewards.isEmpty) {
|
||||
// 보상 없이 건너뛴 경우
|
||||
_pendingReturnReward = null;
|
||||
debugPrint('[ReturnRewards] Reward skipped');
|
||||
return;
|
||||
}
|
||||
|
||||
// 골드 추가
|
||||
final updatedInventory = _state!.inventory.copyWith(
|
||||
gold: _state!.inventory.gold + totalGold,
|
||||
);
|
||||
_state = _state!.copyWith(inventory: updatedInventory);
|
||||
var updatedState = _state!;
|
||||
|
||||
// 보상 적용
|
||||
for (final reward in rewards) {
|
||||
switch (reward.type) {
|
||||
case ChestRewardType.equipment:
|
||||
if (reward.equipment != null) {
|
||||
// 현재 장비와 비교하여 더 좋으면 자동 장착
|
||||
final slotIndex = reward.equipment!.slot.index;
|
||||
final currentItem = updatedState.equipment.getItemByIndex(slotIndex);
|
||||
if (currentItem.isEmpty ||
|
||||
reward.equipment!.itemWeight > currentItem.itemWeight) {
|
||||
updatedState = updatedState.copyWith(
|
||||
equipment: updatedState.equipment.setItemByIndex(
|
||||
slotIndex,
|
||||
reward.equipment!,
|
||||
),
|
||||
);
|
||||
debugPrint('[ReturnRewards] Equipped: ${reward.equipment!.name}');
|
||||
} else {
|
||||
// 더 좋지 않으면 판매 (골드로 변환)
|
||||
final sellPrice =
|
||||
(reward.equipment!.level * 50 * 0.3).round().clamp(1, 99999);
|
||||
updatedState = updatedState.copyWith(
|
||||
inventory: updatedState.inventory.copyWith(
|
||||
gold: updatedState.inventory.gold + sellPrice,
|
||||
),
|
||||
);
|
||||
debugPrint('[ReturnRewards] Sold: ${reward.equipment!.name} '
|
||||
'for $sellPrice gold');
|
||||
}
|
||||
}
|
||||
case ChestRewardType.potion:
|
||||
if (reward.potionId != null) {
|
||||
updatedState = updatedState.copyWith(
|
||||
potionInventory: updatedState.potionInventory.addPotion(
|
||||
reward.potionId!,
|
||||
reward.potionCount ?? 1,
|
||||
),
|
||||
);
|
||||
debugPrint('[ReturnRewards] Added potion: ${reward.potionId} '
|
||||
'x${reward.potionCount}');
|
||||
}
|
||||
case ChestRewardType.gold:
|
||||
if (reward.gold != null && reward.gold! > 0) {
|
||||
updatedState = updatedState.copyWith(
|
||||
inventory: updatedState.inventory.copyWith(
|
||||
gold: updatedState.inventory.gold + reward.gold!,
|
||||
),
|
||||
);
|
||||
debugPrint('[ReturnRewards] Added gold: ${reward.gold}');
|
||||
}
|
||||
case ChestRewardType.experience:
|
||||
if (reward.experience != null && reward.experience! > 0) {
|
||||
updatedState = updatedState.copyWith(
|
||||
progress: updatedState.progress.copyWith(
|
||||
exp: updatedState.progress.exp.copyWith(
|
||||
position:
|
||||
updatedState.progress.exp.position + reward.experience!,
|
||||
),
|
||||
),
|
||||
);
|
||||
debugPrint('[ReturnRewards] Added experience: ${reward.experience}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_state = updatedState;
|
||||
|
||||
// 저장
|
||||
unawaited(saveManager.saveState(
|
||||
@@ -761,7 +836,7 @@ class GameSessionController extends ChangeNotifier {
|
||||
_pendingReturnReward = null;
|
||||
notifyListeners();
|
||||
|
||||
debugPrint('[ReturnRewards] Reward applied: $totalGold gold');
|
||||
debugPrint('[ReturnRewards] Rewards applied: ${rewards.length} items');
|
||||
}
|
||||
|
||||
/// 복귀 보상 건너뛰기
|
||||
|
||||
Reference in New Issue
Block a user