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> Function() getAvailableSpeeds, }) : _cheatsEnabledGetter = cheatsEnabledGetter, _getAvailableSpeeds = getAvailableSpeeds; final bool Function() _cheatsEnabledGetter; final Future> 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 speeds, int initialSpeed}) calculateInitialSpeeds({ required List baseAvailableSpeeds, required int baseSpeed, }) { if (_isSpeedBoostActive) { // 부스트 상태: 부스트 배속만 사용, 기본 배속 저장 savedSpeedMultiplier = baseSpeed; return (speeds: [speedBoostMultiplier], initialSpeed: speedBoostMultiplier); } // 일반 상태: 기본 배속 사용 return (speeds: baseAvailableSpeeds, initialSpeed: baseSpeed); } }