refactor(controller): GameSessionController 분할 (920→526 LOC)
- GameStatisticsManager: 세션/누적 통계 추적 - SpeedBoostManager: 광고 배속 부스트 기능 - ReturnRewardsManager: 복귀 보상 기능 - ResurrectionManager: 사망/부활 처리 - HallOfFameManager: 명예의 전당 관리
This commit is contained in:
211
lib/src/features/game/managers/speed_boost_manager.dart
Normal file
211
lib/src/features/game/managers/speed_boost_manager.dart
Normal file
@@ -0,0 +1,211 @@
|
||||
import 'package:asciineverdie/src/core/engine/ad_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/iap_service.dart';
|
||||
import 'package:asciineverdie/src/core/engine/progress_loop.dart';
|
||||
import 'package:asciineverdie/src/core/model/monetization_state.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 속도 부스트(광고 배속) 기능 관리자
|
||||
///
|
||||
/// 광고 시청 후 일정 시간 동안 게임 속도를 높이는 기능을 담당합니다.
|
||||
/// 게임 시간(elapsedMs) 기준으로 종료 시점을 판정합니다.
|
||||
class SpeedBoostManager {
|
||||
SpeedBoostManager({
|
||||
required bool Function() cheatsEnabledGetter,
|
||||
required Future<List<int>> Function() getAvailableSpeeds,
|
||||
}) : _cheatsEnabledGetter = cheatsEnabledGetter,
|
||||
_getAvailableSpeeds = getAvailableSpeeds;
|
||||
|
||||
final bool Function() _cheatsEnabledGetter;
|
||||
final Future<List<int>> Function() _getAvailableSpeeds;
|
||||
|
||||
// 속도 부스트 상태
|
||||
bool _isSpeedBoostActive = false;
|
||||
static const int _speedBoostDuration = 300; // 5분 (게임 시간 기준, 초)
|
||||
|
||||
// 광고 표시 중 플래그 (lifecycle reload 방지용)
|
||||
bool _isShowingAd = false;
|
||||
int _adEndTimeMs = 0; // 광고 종료 시점 (밀리초)
|
||||
|
||||
/// 배속 저장 (pause/resume 시 유지)
|
||||
int savedSpeedMultiplier = 1;
|
||||
|
||||
/// 상태 변경 알림 콜백
|
||||
VoidCallback? onStateChanged;
|
||||
|
||||
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
|
||||
int get speedBoostMultiplier =>
|
||||
(kDebugMode && _cheatsEnabledGetter()) ? 20 : 5;
|
||||
|
||||
/// 속도 부스트 활성화 여부
|
||||
bool get isSpeedBoostActive => _isSpeedBoostActive;
|
||||
|
||||
/// 광고 표시 중 여부 (lifecycle reload 방지용)
|
||||
bool get isShowingAd => _isShowingAd;
|
||||
|
||||
/// 최근 광고를 시청했는지 여부 (1초 이내)
|
||||
bool get isRecentlyShowedAd {
|
||||
if (_adEndTimeMs == 0) return false;
|
||||
return DateTime.now().millisecondsSinceEpoch - _adEndTimeMs < 1000;
|
||||
}
|
||||
|
||||
/// 속도 부스트 지속 시간 (초)
|
||||
int get speedBoostDuration => _speedBoostDuration;
|
||||
|
||||
/// 속도 부스트 남은 시간 (초) - 게임 시간(elapsedMs) 기준 계산
|
||||
int getRemainingSeconds(MonetizationState monetization, int currentElapsedMs) {
|
||||
if (!_isSpeedBoostActive) return 0;
|
||||
final endMs = monetization.speedBoostEndMs;
|
||||
if (endMs == null) return 0;
|
||||
final remainingMs = endMs - currentElapsedMs;
|
||||
return remainingMs > 0 ? (remainingMs / 1000).ceil() : 0;
|
||||
}
|
||||
|
||||
/// 현재 실제 배속 (부스트 적용 포함)
|
||||
int getCurrentSpeedMultiplier(ProgressLoop? loop) {
|
||||
if (_isSpeedBoostActive) return speedBoostMultiplier;
|
||||
return loop?.speedMultiplier ?? savedSpeedMultiplier;
|
||||
}
|
||||
|
||||
/// 속도 부스트 활성화 (광고 시청 후)
|
||||
///
|
||||
/// 유료 유저: 무료 활성화
|
||||
/// 무료 유저: 인터스티셜 광고 시청 후 활성화
|
||||
/// Returns: (활성화 성공 여부, 업데이트된 monetization)
|
||||
Future<(bool, MonetizationState)> activateSpeedBoost({
|
||||
required ProgressLoop? loop,
|
||||
required MonetizationState monetization,
|
||||
required int currentElapsedMs,
|
||||
}) async {
|
||||
if (_isSpeedBoostActive) return (false, monetization);
|
||||
if (loop == null) return (false, monetization);
|
||||
|
||||
// 유료 유저는 무료 활성화
|
||||
if (IAPService.instance.isAdRemovalPurchased) {
|
||||
final updatedMonetization = _startSpeedBoost(
|
||||
loop: loop,
|
||||
monetization: monetization,
|
||||
currentElapsedMs: currentElapsedMs,
|
||||
);
|
||||
debugPrint('[SpeedBoost] Activated (paid user)');
|
||||
return (true, updatedMonetization);
|
||||
}
|
||||
|
||||
// 무료 유저는 인터스티셜 광고 필요
|
||||
MonetizationState updatedMonetization = monetization;
|
||||
bool activated = false;
|
||||
_isShowingAd = true;
|
||||
|
||||
final adResult = await AdService.instance.showInterstitialAd(
|
||||
adType: AdType.interstitialSpeed,
|
||||
onComplete: () {
|
||||
updatedMonetization = _startSpeedBoost(
|
||||
loop: loop,
|
||||
monetization: monetization,
|
||||
currentElapsedMs: currentElapsedMs,
|
||||
);
|
||||
activated = true;
|
||||
},
|
||||
);
|
||||
|
||||
_isShowingAd = false;
|
||||
_adEndTimeMs = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
if (adResult == AdResult.completed || adResult == AdResult.debugSkipped) {
|
||||
debugPrint('[SpeedBoost] Activated (free user with ad)');
|
||||
return (activated, updatedMonetization);
|
||||
}
|
||||
|
||||
debugPrint('[SpeedBoost] Activation failed: $adResult');
|
||||
return (false, monetization);
|
||||
}
|
||||
|
||||
/// 속도 부스트 시작 (내부)
|
||||
MonetizationState _startSpeedBoost({
|
||||
required ProgressLoop? loop,
|
||||
required MonetizationState monetization,
|
||||
required int currentElapsedMs,
|
||||
}) {
|
||||
_isSpeedBoostActive = true;
|
||||
|
||||
// loop가 있으면 현재 배속 저장 및 즉시 적용
|
||||
if (loop != null) {
|
||||
savedSpeedMultiplier = loop.speedMultiplier;
|
||||
loop.updateAvailableSpeeds([speedBoostMultiplier]);
|
||||
}
|
||||
|
||||
// 종료 시점 저장 (게임 시간 기준)
|
||||
final endMs = currentElapsedMs + (_speedBoostDuration * 1000);
|
||||
final updatedMonetization = monetization.copyWith(speedBoostEndMs: endMs);
|
||||
|
||||
debugPrint('[SpeedBoost] Started, ends at $endMs ms');
|
||||
onStateChanged?.call();
|
||||
|
||||
return updatedMonetization;
|
||||
}
|
||||
|
||||
/// 매 틱마다 부스트 만료 체크
|
||||
///
|
||||
/// Returns: 부스트가 종료되었으면 true
|
||||
bool checkExpiry({
|
||||
required int elapsedMs,
|
||||
required MonetizationState monetization,
|
||||
required ProgressLoop? loop,
|
||||
}) {
|
||||
if (!_isSpeedBoostActive) return false;
|
||||
|
||||
final endMs = monetization.speedBoostEndMs;
|
||||
if (endMs != null && elapsedMs >= endMs) {
|
||||
endSpeedBoost(loop: loop);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// 속도 부스트 종료 (외부 호출 가능)
|
||||
void endSpeedBoost({required ProgressLoop? loop}) {
|
||||
_isSpeedBoostActive = false;
|
||||
|
||||
// 원래 배속 복원
|
||||
if (loop != null) {
|
||||
final savedSpeed = savedSpeedMultiplier;
|
||||
|
||||
_getAvailableSpeeds().then((speeds) {
|
||||
loop.updateAvailableSpeeds(speeds);
|
||||
loop.setSpeed(savedSpeed);
|
||||
debugPrint('[SpeedBoost] Speed restored to ${savedSpeed}x');
|
||||
});
|
||||
}
|
||||
|
||||
onStateChanged?.call();
|
||||
debugPrint('[SpeedBoost] Ended');
|
||||
}
|
||||
|
||||
/// 속도 부스트 수동 취소
|
||||
///
|
||||
/// Returns: 업데이트된 monetization
|
||||
MonetizationState cancelSpeedBoost({
|
||||
required ProgressLoop? loop,
|
||||
required MonetizationState monetization,
|
||||
}) {
|
||||
if (_isSpeedBoostActive) {
|
||||
endSpeedBoost(loop: loop);
|
||||
}
|
||||
return monetization.copyWith(speedBoostEndMs: null);
|
||||
}
|
||||
|
||||
/// 부스트 상태에 따른 초기 배속 설정 계산
|
||||
///
|
||||
/// startNew() 호출 시 사용
|
||||
({List<int> speeds, int initialSpeed}) calculateInitialSpeeds({
|
||||
required List<int> baseAvailableSpeeds,
|
||||
required int baseSpeed,
|
||||
}) {
|
||||
if (_isSpeedBoostActive) {
|
||||
// 부스트 상태: 부스트 배속만 사용, 기본 배속 저장
|
||||
savedSpeedMultiplier = baseSpeed;
|
||||
return (speeds: [speedBoostMultiplier], initialSpeed: speedBoostMultiplier);
|
||||
}
|
||||
// 일반 상태: 기본 배속 사용
|
||||
return (speeds: baseAvailableSpeeds, initialSpeed: baseSpeed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user