using System.Collections; using System.Collections.Generic; using BlockOutReplica.Presentation; using UnityEngine; namespace BlockOutReplica.Gameplay { public sealed partial class BlockOutGameController { private BlockOutPlayerState player; private BlockOutEconomy economy; private GameModal modalReturn; private bool lifeCharged, completionStarted, completionCommitted, winReturnsHome; private int playOnCount, wonLevel, awardedReward; private float freezeRemaining; private BlockOutGameAudio gameAudio; public GameModal Modal { get; private set; } public BoosterType SelectedBooster { get; private set; } public BoosterType PurchaseBooster { get; private set; } public string Notice { get; private set; } public float FreezeRemaining => freezeRemaining; public bool HasStarted => timerRunning; public int WonLevel => wonLevel; public int WinReward => awardedReward; public int LevelRewardPreview => economy.RegularLevelWinCoinReward; public int ContinueCost => economy.ContinuePrice(playOnCount); public int LifeRefillCost => economy.refillPrice; public int BoosterCost => economy.Price(PurchaseBooster); public int PriceOfBooster(BoosterType type) => economy.Price(type); public bool AllLevelsCompleted => progress != null && progress.CatalogCompleted; public BlockOutPlayerState Player { get { if (player == null) { economy = BlockOutEconomy.Load(); player = new BlockOutPlayerState(economy); } return player; } } private void InitializeInteractions() { _ = Player; gameAudio = GetComponent(); if (gameAudio == null) gameAudio = gameObject.AddComponent(); gameAudio.Initialize(player); } private void ResetRunInteractions() { Modal = GameModal.None; SelectedBooster = BoosterType.None; BoosterPhase = ""; BoosterPlaybackTime=0; FreezeAnimationTime=0; ActiveBoosterParticleCount=0;ActiveFreezeParticleCount=0;HasNativeFreezeScreen=false; Notice = ""; freezeRemaining = 0f; playOnCount = 0; lifeCharged = completionStarted = completionCommitted = false; awardedReward = 0; if (gameAudio != null) gameAudio.SetHome(false); } private void AdvanceClock(float seconds) { if (player != null) player.RefreshLives(); if (!timerRunning || IsPaused || ScreenState != BlockOutScreenState.Playing || Modal != GameModal.None || SelectedBooster != BoosterType.None || pieces.Count == 0) return; float frozen = Mathf.Min(freezeRemaining, seconds); freezeRemaining -= frozen; TimeLeft = Mathf.Max(0f, TimeLeft - (seconds - frozen)); if (TimeLeft <= 0f && activeExits == 0 && !completionStarted) { CancelDragForMenu(); SelectedBooster = BoosterType.None; OpenModal(GameModal.OutOfTime); Status = "时间到,可以续关或放弃本局"; PlayUiSound("Timer_Warning"); } } private void CancelDragForMenu() { if (draggingPiece == null) return; draggingPiece.SetDragging(false); SetDoorHighlight(draggingPiece.ColorId, false); draggingPiece = null; hasPreviousPointerPosition = false; } private void SetPaused(bool value) { IsPaused = value; foreach (DoorView door in doors) if (door != null) door.IsEffectPaused = value; if(boardRoot!=null) foreach(var curtain in boardRoot.GetComponentsInChildren())curtain.IsPaused=value; } private void OpenModal(GameModal modal) { CancelDragForMenu(); Modal = modal; Notice = ""; SetPaused(ScreenState == BlockOutScreenState.Playing); } public void CloseModal() { if (Modal == GameModal.OutOfTime) { OpenModal(GameModal.GiveUp); return; } if (Modal == GameModal.Failed) { GoHome(); return; } if (Modal == GameModal.Restart || Modal == GameModal.Quit) { GameModal back = modalReturn; Modal = GameModal.None; if (back != GameModal.None) { OpenModal(back); return; } } else if (Modal == GameModal.GiveUp) { OpenModal(GameModal.OutOfTime); return; } else Modal = GameModal.None; Notice = ""; SetPaused(false); } public void RequestPause() { if (ScreenState != BlockOutScreenState.Playing || completionStarted) return; if (Modal == GameModal.Pause) { CloseModal(); return; } if (Modal != GameModal.None) return; SelectedBooster = BoosterType.None; OpenModal(GameModal.Pause); } public void RequestRestart() { if (ScreenState != BlockOutScreenState.Playing || completionStarted) return; if (Modal != GameModal.None && Modal != GameModal.Pause) return; modalReturn = Modal; SelectedBooster = BoosterType.None; OpenModal(GameModal.Restart); } public void RequestQuit() { if (ScreenState != BlockOutScreenState.Playing || completionStarted) return; if (Modal != GameModal.None && Modal != GameModal.Pause) return; modalReturn = Modal; SelectedBooster = BoosterType.None; OpenModal(GameModal.Quit); } private void ChargeLifeOnce(bool force=false) { if (lifeCharged || (!timerRunning&&!force)) return; Player.LoseLife(); lifeCharged = true; } public void ConfirmRestart() { if (Modal != GameModal.Restart) return; ChargeLifeOnce(); if (Player.Lives <= 0 && !Player.HasInfiniteLives) { GoHome(); OpenModal(GameModal.Lives); return; } LoadCurrentLevel(); } public void ConfirmQuit() { if (Modal != GameModal.Quit) return; // Original L10 runtime (2026-10-20): even a freshly entered board // with no drag goes through FAILED; closing FAILED returns Home. ChargeLifeOnce(true); SelectedBooster=BoosterType.None; ScreenState=BlockOutScreenState.Failed;Modal=GameModal.Failed;SetPaused(true); } public void ContinueWithCoins() { if (Modal != GameModal.OutOfTime && Modal != GameModal.GiveUp) return; if (!Player.SpendCoins(ContinueCost)) { Notice = "Not enough coins"; return; } playOnCount++; TimeLeft += economy.PlayOnExtraTimeSeconds; Modal = GameModal.None; Notice = ""; SetPaused(false); timerRunning = true; Status = "+30 seconds"; } public void GiveUpRun() { if (Modal != GameModal.GiveUp) return; ChargeLifeOnce(); SelectedBooster = BoosterType.None; ScreenState = BlockOutScreenState.Failed; Modal = GameModal.Failed; SetPaused(true); } public void RetryFailedLevel() { if (Modal != GameModal.Failed || ScreenState != BlockOutScreenState.Failed) return; if (Player.Lives <= 0 && !Player.HasInfiniteLives) { GoHome(); OpenModal(GameModal.Lives); return; } LoadCurrentLevel(); } public void RequestEnterLevel() { if (ScreenState != BlockOutScreenState.Home || Modal != GameModal.None) return; if (AllLevelsCompleted) { OpenModal(GameModal.Info); Notice = "All recovered levels completed"; return; } OpenModal(Player.Lives > 0 || Player.HasInfiniteLives ? GameModal.PreLevel : GameModal.Lives); } public void EnterPreparedLevel() { if (ScreenState != BlockOutScreenState.Home || Modal != GameModal.PreLevel) return; if (Player.Lives <= 0 && !Player.HasInfiniteLives) { OpenModal(GameModal.Lives); return; } LoadCurrentLevel(); #if UNITY_EDITOR if(IsManualLoopSandbox)TimeLeft=1800; // UI review only; automated real-board loop retains 180s. #endif } public void GoHome(bool settleJourney = true) { StopAllCoroutines(); CancelDragForMenu(); ClearGenerated(); pieces.Clear(); doors.Clear(); activeExits = 0; SelectedBooster = BoosterType.None; Modal = GameModal.None; InputLocked = false; SetPaused(false); ScreenState = BlockOutScreenState.Home; JourneyTab = false; JourneyRevision++; timerRunning = false; freezeRemaining = 0f; Status = ""; ActiveBoosterParticleCount=0;ActiveFreezeParticleCount=0;HasNativeFreezeScreen=false; if (gameAudio != null) gameAudio.SetHome(true); if (settleJourney) SettleJourneyOnHome(); } public void RequestLives() { if (ScreenState == BlockOutScreenState.Home && Modal == GameModal.None) OpenModal(GameModal.Lives); } public void BuyLifeRefill() { if (Modal != GameModal.Lives) return; Notice = Player.Lives == Player.MaxLives ? "Lives are full" : Player.RefillLives() ? "Lives refilled" : "Not enough coins"; } public void RequestSettings() { if (ScreenState == BlockOutScreenState.Home && Modal == GameModal.None) OpenModal(GameModal.Settings); } public void RequestCoinInfo() { if (ScreenState == BlockOutScreenState.Home && Modal == GameModal.None) { OpenModal(GameModal.Info); Notice = "Coins are earned by winning levels. Real-money purchases are not connected."; } } public void ToggleSounds() { if (Modal != GameModal.Settings && Modal != GameModal.Pause) return; Player.ToggleSounds(); gameAudio?.ApplySettings(); } public void ToggleMusic() { if (Modal != GameModal.Settings && Modal != GameModal.Pause) return; Player.ToggleMusic(); gameAudio?.ApplySettings(); } public void ToggleHaptics() { if (Modal != GameModal.Settings && Modal != GameModal.Pause) return; Player.ToggleHaptics(); } public void PlayUiSound(string name = "ui-click-v1") { if (gameAudio != null) gameAudio.Play(name); } public void ClickBooster(BoosterType type) { if (type == BoosterType.None || ScreenState != BlockOutScreenState.Playing || Modal != GameModal.None || IsPaused || InputLocked || activeExits != 0 || TimeLeft <= 0f || completionStarted) return; CancelDragForMenu(); if (SelectedBooster == type) { SelectedBooster = BoosterType.None; Status = ""; return; } SelectedBooster = BoosterType.None; if (type == BoosterType.Freeze && freezeRemaining > 0f) { Status = "Freeze is already active"; return; } if (!Player.CanUseBooster(type)) { ShowBoosterFundsNotice(type); return; } if (type == BoosterType.Freeze) { if (!Player.SpendBoosterUse(type)) return; freezeRemaining = economy.TimeFreezeDurationAsSeconds; timerRunning = true; Status = "Timer frozen for 10 seconds"; PlayUiSound("Timer_Freeze"); StartCoroutine(PlayFreezeBooster()); return; } SelectedBooster = type; Status = type == BoosterType.Rocket ? "Select a block to eliminate" : "Select a color to eliminate"; } public void CancelBoosterSelection() { if (!InputLocked) { SelectedBooster = BoosterType.None; Status = ""; } } private void ShowBoosterFundsNotice(BoosterType type) { PurchaseBooster=type; OpenModal(GameModal.Info); // Observed original L10 opens Shop here. Billing is intentionally // unconnected; do not imitate a working purchase or grant currency. Notice="Not enough coins\n"+economy.Price(type)+" coins required\nShop is not connected."; } public void BuySelectedBooster() { if (Modal != GameModal.BoosterPurchase) return; if (!Player.BuyBooster(PurchaseBooster)) { Notice = "Not enough coins"; return; } CloseModal(); Status = "Booster added to inventory"; } private void ApplyBoosterTarget(BlockPieceView target) { BoosterType type = SelectedBooster; if (target == null || !pieces.Contains(target) || (type != BoosterType.Rocket && type != BoosterType.Ufo) || activeExits != 0) return; if(!SpecialCanTargetBooster(target))return; if(!Player.SpendBoosterUse(type)){SelectedBooster=BoosterType.None;ShowBoosterFundsNotice(type);return;} SelectedBooster = BoosterType.None; timerRunning = true; InputLocked = true; #if UNITY_ANDROID || UNITY_IOS if (Player.Haptics) Handheld.Vibrate(); #endif List targets = type == BoosterType.Rocket ? new List {target} : pieces.FindAll(item => item.ColorId == target.ColorId && SpecialCanTargetBooster(item)); foreach (var item in targets) { SpecialBeginRemoval(item, true); pieces.Remove(item); activeExits++; foreach (Collider collider in item.GetComponentsInChildren()) collider.enabled = false; } StartCoroutine(UseTargetBooster(type, targets)); } private IEnumerator UseTargetBooster(BoosterType type, List targets) { yield return PlayRecoveredTargetBooster(type,targets); } private void CloseUnusedDoors() { foreach (DoorView door in doors) if (door != null && !SpecialHasRemainingColor(door.ColorId)) door.MarkCleared(); } private IEnumerator FinishBoardIfCleared() { if (pieces.Count != 0 || activeExits != 0 || completionStarted || !SpecialCanFinishLevel()) yield break; completionStarted = true; timerRunning = false; InputLocked = true; while (doors.Exists(item => item != null && item.IsExitEffectPlaying)) yield return null; SpawnCompletionBurst(); yield return CompleteLevelFlow(); } public void ContinueAfterWin() { if (ScreenState != BlockOutScreenState.LevelComplete || !completionCommitted) return; // GoHome automatically settles queued Journey rewards. Do not force // the Journey tab on every ordinary win; original tab order unverified. if (winReturnsHome || AllLevelsCompleted) GoHome(); else LoadCurrentLevel(); } } }