fix(monetization): 버프 종료 버그 수정 (게임 시간 기준 통일)
- 배속 부스트: 실시간 타이머 → 게임 시간(elapsedMs) 기준 종료 - 자동부활 버프: 만료 시 autoReviveEndMs null 초기화 추가 - 매 틱마다 _checkSpeedBoostExpiry(), _checkAutoReviveExpiry() 호출 - 광고 직후 앱 resume 시 reload 방지 (isRecentlyShowedAd) - 앱 pause/reload와 무관하게 버프 정상 종료
This commit is contained in:
@@ -61,13 +61,13 @@ class GameSessionController extends ChangeNotifier {
|
||||
bool _autoResurrect = false;
|
||||
|
||||
// 속도 부스트 상태 (Phase 6)
|
||||
// 실시간 타이머 대신 게임 시간(elapsedMs) 기준으로 종료 판정
|
||||
bool _isSpeedBoostActive = false;
|
||||
Timer? _speedBoostTimer;
|
||||
int _speedBoostRemainingSeconds = 0;
|
||||
static const int _speedBoostDuration = 300; // 5분
|
||||
static const int _speedBoostDuration = 300; // 5분 (게임 시간 기준)
|
||||
|
||||
// 광고 표시 중 플래그 (lifecycle reload 방지용)
|
||||
bool _isShowingAd = false;
|
||||
int _adEndTimeMs = 0; // 광고 종료 시점 (밀리초)
|
||||
|
||||
/// 광고 배속 배율 (릴리즈: 5x, 디버그빌드+디버그모드: 20x)
|
||||
int get _speedBoostMultiplier => (kDebugMode && _cheatsEnabled) ? 20 : 5;
|
||||
@@ -153,18 +153,31 @@ class GameSessionController extends ChangeNotifier {
|
||||
}
|
||||
_initPreviousValues(state);
|
||||
|
||||
// 명예의 전당 체크 → 가용 배속 결정
|
||||
final availableSpeeds = await _getAvailableSpeeds();
|
||||
// 명예의 전당 체크 → 기본 가용 배속 결정
|
||||
final baseAvailableSpeeds = await _getAvailableSpeeds();
|
||||
final hasHallOfFame = baseAvailableSpeeds.contains(2);
|
||||
|
||||
// 명예의 전당 해금 시 기본 2배속, 아니면 1배속
|
||||
final hasHallOfFame = availableSpeeds.contains(2);
|
||||
// 새 게임이면 기본 배속, 세이브 로드 시 명예의 전당 해금 시 최소 2배속 보장
|
||||
final int initialSpeed;
|
||||
// 기본 배속 결정 (부스트 미적용 시)
|
||||
final int baseSpeed;
|
||||
if (isNewGame) {
|
||||
initialSpeed = hasHallOfFame ? 2 : 1;
|
||||
baseSpeed = hasHallOfFame ? 2 : 1;
|
||||
} else {
|
||||
// 세이브 로드: 명예의 전당 해금 시 최소 2배속
|
||||
initialSpeed = (hasHallOfFame && previousSpeed < 2) ? 2 : previousSpeed;
|
||||
baseSpeed = (hasHallOfFame && previousSpeed < 2) ? 2 : previousSpeed;
|
||||
}
|
||||
|
||||
// 배속 부스트 활성화 상태면 부스트 배속 적용, 아니면 기본 배속
|
||||
final List<int> finalAvailableSpeeds;
|
||||
final int finalInitialSpeed;
|
||||
|
||||
if (_isSpeedBoostActive) {
|
||||
// 부스트 상태: 부스트 배속만 사용, 기본 배속 저장
|
||||
finalAvailableSpeeds = [_speedBoostMultiplier];
|
||||
finalInitialSpeed = _speedBoostMultiplier;
|
||||
_savedSpeedMultiplier = baseSpeed; // 종료 시 복원할 배속 저장
|
||||
} else {
|
||||
// 일반 상태: 기본 배속 사용
|
||||
finalAvailableSpeeds = baseAvailableSpeeds;
|
||||
finalInitialSpeed = baseSpeed;
|
||||
}
|
||||
|
||||
_loop = ProgressLoop(
|
||||
@@ -177,11 +190,15 @@ class GameSessionController extends ChangeNotifier {
|
||||
cheatsEnabled: cheatsEnabled,
|
||||
onPlayerDied: _onPlayerDied,
|
||||
onGameComplete: _onGameComplete,
|
||||
availableSpeeds: availableSpeeds,
|
||||
initialSpeedMultiplier: initialSpeed,
|
||||
availableSpeeds: finalAvailableSpeeds,
|
||||
initialSpeedMultiplier: finalInitialSpeed,
|
||||
);
|
||||
|
||||
_subscription = _loop!.stream.listen((next) {
|
||||
final elapsedMs = next.skillSystem.elapsedMs;
|
||||
// 버프 만료 체크 (게임 시간 기준)
|
||||
_checkSpeedBoostExpiry(elapsedMs);
|
||||
_checkAutoReviveExpiry(elapsedMs);
|
||||
_updateStatistics(next);
|
||||
_state = next;
|
||||
notifyListeners();
|
||||
@@ -307,6 +324,7 @@ class GameSessionController extends ChangeNotifier {
|
||||
/// 일시 정지 상태에서 재개
|
||||
Future<void> resume() async {
|
||||
if (_state == null || _status != GameSessionStatus.idle) return;
|
||||
// 배속 부스트 상태는 startNew() 내에서 자동 처리됨
|
||||
await startNew(_state!, cheatsEnabled: _cheatsEnabled, isNewGame: false);
|
||||
}
|
||||
|
||||
@@ -607,8 +625,22 @@ class GameSessionController extends ChangeNotifier {
|
||||
/// 광고 표시 중 여부 (lifecycle reload 방지용)
|
||||
bool get isShowingAd => _isShowingAd;
|
||||
|
||||
/// 속도 부스트 남은 시간 (초)
|
||||
int get speedBoostRemainingSeconds => _speedBoostRemainingSeconds;
|
||||
/// 최근 광고를 시청했는지 여부 (1초 이내)
|
||||
/// 광고 종료 후 resumed 이벤트가 늦게 발생하는 경우를 처리
|
||||
bool get isRecentlyShowedAd {
|
||||
if (_adEndTimeMs == 0) return false;
|
||||
return DateTime.now().millisecondsSinceEpoch - _adEndTimeMs < 1000;
|
||||
}
|
||||
|
||||
/// 속도 부스트 남은 시간 (초) - 게임 시간(elapsedMs) 기준 계산
|
||||
int get speedBoostRemainingSeconds {
|
||||
if (!_isSpeedBoostActive) return 0;
|
||||
final endMs = _monetization.speedBoostEndMs;
|
||||
if (endMs == null) return 0;
|
||||
final currentMs = _state?.skillSystem.elapsedMs ?? 0;
|
||||
final remainingMs = endMs - currentMs;
|
||||
return remainingMs > 0 ? (remainingMs / 1000).ceil() : 0;
|
||||
}
|
||||
|
||||
/// 속도 부스트 배율
|
||||
int get speedBoostMultiplier => _speedBoostMultiplier;
|
||||
@@ -651,6 +683,7 @@ class GameSessionController extends ChangeNotifier {
|
||||
);
|
||||
|
||||
_isShowingAd = false; // 광고 표시 종료
|
||||
_adEndTimeMs = DateTime.now().millisecondsSinceEpoch; // 종료 시점 기록
|
||||
|
||||
if (adResult == AdResult.completed || adResult == AdResult.debugSkipped) {
|
||||
debugPrint('[GameSession] Speed boost activated (free user with ad)');
|
||||
@@ -662,53 +695,72 @@ class GameSessionController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// 속도 부스트 시작 (내부)
|
||||
///
|
||||
/// 게임 시간(elapsedMs) 기준으로 종료 시점 설정.
|
||||
/// 실시간 타이머 대신 매 틱에서 종료 여부 체크.
|
||||
void _startSpeedBoost() {
|
||||
if (_loop == null) return;
|
||||
|
||||
// 현재 배속 저장
|
||||
_savedSpeedMultiplier = _loop!.speedMultiplier;
|
||||
|
||||
// 부스트 배속 적용
|
||||
_isSpeedBoostActive = true;
|
||||
_speedBoostRemainingSeconds = _speedBoostDuration;
|
||||
|
||||
// monetization 상태에 종료 시점 저장 (UI 표시용)
|
||||
// loop가 있으면 현재 배속 저장 및 즉시 적용
|
||||
if (_loop != null) {
|
||||
_savedSpeedMultiplier = _loop!.speedMultiplier;
|
||||
_loop!.updateAvailableSpeeds([_speedBoostMultiplier]);
|
||||
}
|
||||
|
||||
// 종료 시점 저장 (게임 시간 기준)
|
||||
final currentElapsedMs = _state?.skillSystem.elapsedMs ?? 0;
|
||||
final endMs = currentElapsedMs + (_speedBoostDuration * 1000);
|
||||
_monetization = _monetization.copyWith(speedBoostEndMs: endMs);
|
||||
|
||||
// ProgressLoop에 직접 배속 설정
|
||||
_loop!.updateAvailableSpeeds([_speedBoostMultiplier]);
|
||||
|
||||
// 1초마다 남은 시간 감소
|
||||
_speedBoostTimer?.cancel();
|
||||
_speedBoostTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
_speedBoostRemainingSeconds--;
|
||||
notifyListeners();
|
||||
|
||||
if (_speedBoostRemainingSeconds <= 0) {
|
||||
_endSpeedBoost();
|
||||
}
|
||||
});
|
||||
|
||||
debugPrint('[GameSession] Speed boost started, ends at $endMs ms');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 매 틱마다 부스트 만료 체크
|
||||
///
|
||||
/// 게임 시간(elapsedMs)이 종료 시점을 넘으면 자동 종료.
|
||||
void _checkSpeedBoostExpiry(int elapsedMs) {
|
||||
if (!_isSpeedBoostActive) return;
|
||||
|
||||
final endMs = _monetization.speedBoostEndMs;
|
||||
if (endMs != null && elapsedMs >= endMs) {
|
||||
_endSpeedBoost();
|
||||
}
|
||||
}
|
||||
|
||||
/// 매 틱마다 자동부활 버프 만료 체크
|
||||
///
|
||||
/// 게임 시간(elapsedMs)이 종료 시점을 넘으면 버프 상태 초기화.
|
||||
void _checkAutoReviveExpiry(int elapsedMs) {
|
||||
final endMs = _monetization.autoReviveEndMs;
|
||||
if (endMs == null) return;
|
||||
|
||||
if (elapsedMs >= endMs) {
|
||||
_monetization = _monetization.copyWith(autoReviveEndMs: null);
|
||||
debugPrint('[GameSession] Auto-revive buff expired');
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// 속도 부스트 종료 (내부)
|
||||
void _endSpeedBoost() {
|
||||
_speedBoostTimer?.cancel();
|
||||
_speedBoostTimer = null;
|
||||
_isSpeedBoostActive = false;
|
||||
_speedBoostRemainingSeconds = 0;
|
||||
|
||||
// monetization 상태 초기화 (UI 표시 제거)
|
||||
// monetization 상태 초기화
|
||||
_monetization = _monetization.copyWith(speedBoostEndMs: null);
|
||||
|
||||
// 원래 배속 복원
|
||||
if (_loop != null) {
|
||||
final currentLoop = _loop;
|
||||
final savedSpeed = _savedSpeedMultiplier;
|
||||
|
||||
if (currentLoop != null) {
|
||||
_getAvailableSpeeds().then((speeds) {
|
||||
_loop!.updateAvailableSpeeds(speeds);
|
||||
_loop!.setSpeed(_savedSpeedMultiplier);
|
||||
// 콜백 실행 시점에 loop가 변경되지 않았는지 확인
|
||||
if (_loop == currentLoop) {
|
||||
currentLoop.updateAvailableSpeeds(speeds);
|
||||
currentLoop.setSpeed(savedSpeed);
|
||||
debugPrint('[GameSession] Speed restored to ${savedSpeed}x');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user