using System; using System.Collections; using UnityEngine; public class GameManager : MonoBehaviour { public static event System.Action OnInviteSent; public static event System.Action OnReady; public event System.Action OnInputModeChanged; private IBotAI bot; public bool isReady = false; // Готов ли игрок (расставил ли корабли) public GameManager opponentManager; // Противник (если есть) public CannonBattery battery; public CannonBattery enemyBattery; public GameState currentState; public UIManager uiManager; public GamePole playerField; public GamePole enemyField; public GameObject targetCursorPrefab; private GameObject targetCursorInstance; public bool isPlayerTurn = false; public int playerShips = 0; public int enemyShips = 0; public int playerId = 1; // 1 - первый игрок, 2 - второй игрок и т.д. public Camera playerCamera; public int currentBotLevel = 0; public EnemyManager enemyManager; public FortInitializer fortInitializer; public CameraController cameraController; [Header("Время бездействия")] [SerializeField] float inactionTimer = 360f; [SerializeField] float mainMenuVideoIdleDelay = 30f; public float timerBackToMenu = 0; private bool idleVideoRequested; [SerializeField] private ShipPlacementTimer shipPlacementTimer; private bool autoStartBattlePending; [Header("Таймер бездействия в бою")] [SerializeField] private float battleIdleTimeout = 45f; private float battleIdleTimer = 0f; private bool battlePauseShown = false; /// True после EndGame() — блокирует таймер паузы и вызов StartPlayerTurn() из BotTurn(). private bool isGameEnded; public enum InputMode { UI, Settings, ShipPlacement, Battle } private bool paidFroFriend = false; public InputMode currentInputMode = InputMode.UI; [Header("Настройки бота")] public bool useManualDifficulty = false; public BotDifficulty manualDifficulty = BotDifficulty.Level1; [Header("Конфиг уровней (режимы игры)")] [SerializeField] private BossLevelConfig bossLevelConfig; public enum BotDifficulty { Level1, Level2, Level3, Level4, Level5, Level6, } public GameMode gameMode = GameMode.PlayerVsBot; public BotDifficulty botDifficulty = BotDifficulty.Level1; void Start() { // Запоминаем исходную позицию пивота (центр карты) var player = GetComponentInParent(); if (player != null) playerId = player.PlayerId; SetState(GameState.MainMenu); } private void Update() { timerBackToMenu += Time.deltaTime; bool idleVideoPlaying = IdleMainMenuVideoController.Instance != null && IdleMainMenuVideoController.Instance.IsPlaying(); if (currentState == GameState.MainMenu) { if (!idleVideoPlaying && timerBackToMenu >= mainMenuVideoIdleDelay) { IdleMainMenuVideoController.Instance?.TryStartVideo(); idleVideoRequested = true; } } else { idleVideoRequested = false; if (timerBackToMenu >= inactionTimer) { ReturnToMainMenu(); timerBackToMenu = 0; } } // --- Таймер бездействия во время боя --- if (currentState == GameState.Playing && !battlePauseShown && !isGameEnded) { battleIdleTimer += Time.deltaTime; if (battleIdleTimer >= battleIdleTimeout) { ShowBattlePause(); } } } private void OnEnable() { In.ButtonEvent += ResetTimer; if (shipPlacementTimer != null) shipPlacementTimer.TimerExpired += HandleShipPlacementTimeout; } private void OnDisable() { In.ButtonEvent -= ResetTimer; if (shipPlacementTimer != null) shipPlacementTimer.TimerExpired -= HandleShipPlacementTimeout; } private void ResetTimer() { ResetIdleTimer(); IdleMainMenuVideoController.Instance?.StopVideo(); } public void ResetIdleTimer() { timerBackToMenu = 0; idleVideoRequested = false; } // ===== Таймер бездействия в бою ===== public void ResetBattleIdleTimer() { battleIdleTimer = 0f; } public void ShowBattlePause() { if (battlePauseShown) return; battlePauseShown = true; SetInputMode(InputMode.UI); uiManager.ShowOverlay("BattlePause", true, hideOtherOverlays: true); } public void HideBattlePause() { if (!battlePauseShown) return; battlePauseShown = false; battleIdleTimer = 0f; uiManager.ShowOverlay("BattlePause", false); SetInputMode(InputMode.Battle); } // ===== Панель правил ===== /// /// Открыть панель правил. Вешайте прямо на onClick кнопки «Правила» в Inspector. /// public void OpenRules() { uiManager.ShowOverlay("Rules", true, hideOtherOverlays: true); SetInputMode(InputMode.UI); } void SetPlayerReady(bool ready) { isReady = ready; if (gameMode == GameMode.PlayerVsBot) { uiManager.SetupPlayButtons(ready); } else { var rez = isReady & opponentManager.isReady; uiManager.SetupPlayButtons(rez); opponentManager.uiManager.SetupPlayButtons(rez); } OnReady?.Invoke(playerId, ready, gameMode); TryStartBattleAfterPlacementTimeout(); } public void AddShipPlayer() { playerShips++; if (playerShips == playerField.ShipCount) { SetPlayerReady(true); } } public void SetInputMode(InputMode newMode) { currentInputMode = newMode; OnInputModeChanged?.Invoke(newMode); if (newMode == InputMode.ShipPlacement && targetCursorInstance == null) InitTargetCursor(true); } public void ShipDestroyed(bool isPlayer) { if (isPlayer) { enemyManager.TriggerEventReaction(DialogueEventType.PlayerShipDestroyed); playerShips--; } else { enemyManager.TriggerEventReaction(DialogueEventType.BotShipDestroyed); enemyShips--; } CheckVictoryCondition(); } private void CheckVictoryCondition() { BlinkController.StartBlinkATimes(playerId,5,0.2f); BlinkController.StartBlinkCTimes(5,0.2f); if (playerShips <= 0) { Debug.Log("Игрок проиграл!"); BlinkController.LEDRedAll(playerId); // Красный LED при поражении (статический) enemyManager.TriggerEndGameReaction(false); EndGame(); } else if (enemyShips <= 0) { Debug.Log("Игрок победил!"); BlinkController.SetLEDBaseCustomColor(playerId, 255, 215, 0); // Устанавливаем золотой как базовый enemyManager.TriggerEndGameReaction(true); EndGame(); } } public void ProcessShotResult(bool hit, bool isPlayer, bool wasShipDestroyed = false) { if (wasShipDestroyed) { BlinkController.StartBlinkATimes(playerId,3,0.2f); // Мигание LED 3 раза при потоплении if (!isPlayer) { // Бот потопил корабль игрока - красный 3 раза BlinkController.BlinkLEDRed(playerId, 3, 0.4f); } else { // Игрок потопил корабль противника - зеленый 3 раза BlinkController.BlinkLEDGreen(playerId, 3, 0.4f); } ShipDestroyed(!isPlayer); return; } if (!isPlayer) { if (hit) { BlinkController.StartBlinkATimes(playerId,1); BlinkController.BlinkLEDRed(playerId, 1, 0.6f); enemyManager.TriggerEventReaction(DialogueEventType.BotHit); } else { SoundManager.Instance.PlayMiss(playerId); enemyManager.TriggerEventReaction(DialogueEventType.BotMiss); } } else { if (hit) { BlinkController.StartBlinkATimes(playerId,1); BlinkController.BlinkLEDGreen(playerId, 1, 0.6f); enemyManager.TriggerEventReaction(DialogueEventType.PlayerHit); } else { SoundManager.Instance.PlayMiss(playerId); enemyManager.TriggerEventReaction(DialogueEventType.PlayerMiss); } } } public void SetState(GameState newState) { currentState = newState; // Сброс таймера бездействия в бою при выходе из боя if (newState != GameState.Playing) { battleIdleTimer = 0f; battlePauseShown = false; } if (newState != GameState.ShipPlacement) { autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); } // UI: просто показываем панель по имени state (без switch) uiManager.ShowById(newState.ToString()); // LED подсветка в зависимости от состояния if (newState == GameState.MainMenu) { // В главном меню: канал 1 - волна, канал 2 - радуга BlinkController.LEDWave(1, playerId); // Канал 1 -> волна (команда 8111) BlinkController.LEDRainbow(2, playerId); // Канал 2 -> радуга (команда 8209) } // Камера — оставляем switch только для 3 точек switch (newState) { case GameState.MainMenu: cameraController.MoveCameraToMainMenu(); break; case GameState.ShipPlacement: cameraController.MoveCameraToShipPlacement(); break; case GameState.Playing: cameraController.MoveCameraToGameplay(); break; } } public void StartShipPlacementCountdown() { autoStartBattlePending = false; shipPlacementTimer?.StartTimer(); } private BotDifficulty GetDifficultyFromLevel() { if (useManualDifficulty) { return manualDifficulty; } int level = Mathf.Clamp(currentBotLevel, 0, 4); Debug.Log("шанс на победу : " + DifficultyManager.GetChance()); if (level == 4) // дошел до 5го уровня { // Система шанса на победу if (DifficultyManager.GetChance() <= 0) { // Счётчик НЕ сбрасываем — он сбросится только после победы игрока. // Пока не победил — каждая игра остаётся шанс-игрой (Level5). Debug.Log("Шанс на победу! (сбросится при победе)"); return BotDifficulty.Level5; // даем шанс победить } else { DifficultyManager.DecreaseChance(); return BotDifficulty.Level6; // почти непобедимый } } return (BotDifficulty)level; } public void PrepareForBattle() { if (playerShips != playerField.ShipCount) { playerField.ArrangeShip(); playerShips = playerField.ShipCount; } if (targetCursorInstance != null) { Destroy(targetCursorInstance); targetCursorInstance = null; } SetInputMode(InputMode.Battle); InitTargetCursor(); // Устанавливаем синий цвет LED при входе в бой BlinkController.SetLEDBaseColor(playerId, 5); // 5 = синий } public void StartBattle() { Debug.Log("Игровой этап начат"); autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); if (gameMode == GameMode.PlayerVsPlayer) { if (!isReady || opponentManager == null || !opponentManager.isReady) { Debug.Log("Ожидаем готовности оппонента..."); return; } PrepareForBattle(); opponentManager.PrepareForBattle(); SetState(GameState.Playing); opponentManager.SetState(GameState.Playing); // копируем корабли на поля if (!enemyField.IsReady) { enemyField.CopyShipsFrom(opponentManager.playerField, true); enemyShips = opponentManager.playerField.ShipCount; } if (!opponentManager.enemyField.IsReady) { opponentManager.enemyField.CopyShipsFrom(playerField, true); opponentManager.enemyShips = playerField.ShipCount; } paidFroFriend = false; opponentManager.paidFroFriend = false; opponentManager.uiManager.ShowTimer(false); isReady = false; opponentManager.isReady = false; } else // бот { PrepareForBattle(); enemyShips = enemyField.ShipCount; enemyField.ArrangeShip(true); SetState(GameState.Playing); } SetInputMode(InputMode.Battle); StartPlayerTurn(); } public void InitTargetCursor(bool isPlacingShips = false) { if (targetCursorInstance != null) return; targetCursorInstance = Instantiate(targetCursorPrefab); TargetCursor cursorScript = targetCursorInstance.GetComponent(); cursorScript.Initialize(this, cameraController.pivot, playerId, isPlacingShips); targetCursorInstance.SetActive(isPlacingShips); // по дефолту скрыт } public void EndGame() { isGameEnded = true; SetInputMode(InputMode.UI); autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); StartCoroutine(EndGameSequence()); } private IEnumerator EndGameSequence() { bool isWin = enemyShips <= 0; bool isBotGame = (gameMode == GameMode.PlayerVsBot); // Задержка перед показом GameOver экрана yield return new WaitForSeconds(0.5f); // Определяем приз ДО повышения уровня Prize pendingPrize = null; bool isAtFinalBoss = (currentBotLevel >= 4); if (gameMode == GameMode.PlayerVsBot && isWin) { Prize priz = PrizeSerializer.GetPrizes()[currentBotLevel]; Debug.Log("Приз :" + priz); bool settingEnabled = SettingsManager.instance.GetSettingValue("Выдача призов при игре с ботом") == "Выдавать"; if (settingEnabled && priz.PrizeType != PrizeType.None) pendingPrize = priz; // Повышаем уровень бота ПЕРЕД выдачей приза. if (currentBotLevel < 4) currentBotLevel++; else DifficultyManager.ResetChance(); } // Показываем GameOver; если есть приз и не финальный босс — кнопка "Меню" станет "Забрать приз" bool showClaimPrize = (pendingPrize != null && !isAtFinalBoss); uiManager.ShowGameOverMenu(isWin, isBotGame, showClaimPrize ? pendingPrize : null); // Финальный босс — приз выдаём сразу как раньше (кнопки "Продолжить" нет) if (pendingPrize != null && isAtFinalBoss) { yield return new WaitForSeconds(1.5f); if (pendingPrize.PrizeType == PrizeType.Special) { uiManager.ShowOverlayAnimated("QRCode", 0f); yield break; } if (pendingPrize.PrizeType == PrizeType.Showcase) { uiManager.ShowOverlayAnimated("PrizeSelection", 0f); yield break; } uiManager.ShowOverlayAnimated("PrizeReminder", 0f); PrizeSystem.GivePrize(pendingPrize, playerId); } } // ============= buttons ========================================================================== public void OnReplayPressed() // по идеее функции разные так как нужна проверка на баланс $ { if (gameMode == GameMode.PlayerVsBot) { StartBotGame(isReplay: true); // скидка за реванш обрабатывается в CreditSystem.PayBotGame } else { bool isFreeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл"; if (!isFreeMode) { int price = CreditSystem.PlayerGamePrice; int half = price / 2; if (CreditSystem.IsSharedBalance()) { if (CreditSystem.GetPlayerCredits(1) < price) { uiManager?.ShowOverlay("InsufficientFunds", true, hideOtherOverlays: false); return; } CreditSystem.Pay(price, 1); } else { int myCredits = CreditSystem.GetPlayerCredits(playerId); int oppCredits = CreditSystem.GetPlayerCredits(opponentManager.playerId); // Проверяем в сумме if (myCredits + oppCredits < price) { uiManager?.ShowOverlay("InsufficientFunds", true, hideOtherOverlays: false); return; } if (myCredits >= half && oppCredits >= (price - half)) { CreditSystem.Pay(half, playerId); CreditSystem.Pay(price - half, opponentManager.playerId); } else if (oppCredits >= half && myCredits >= (price - half)) { CreditSystem.Pay(price - half, playerId); CreditSystem.Pay(half, opponentManager.playerId); } else if (myCredits >= price) { opponentManager.paidFroFriend = true; CreditSystem.Pay(price, playerId); } else if (oppCredits >= price) { paidFroFriend = true; CreditSystem.Pay(price, opponentManager.playerId); } else { // Комбинированная оплата int myShare = Mathf.Min(myCredits, price - oppCredits); int oppShare = price - myShare; if (myShare > 0) CreditSystem.Pay(myShare, playerId); if (oppShare > 0) CreditSystem.Pay(oppShare, opponentManager.playerId); } } } opponentManager.StartPlayerGame(); StartPlayerGame(); } } public void OnContinuePressed() { if (gameMode == GameMode.PlayerVsBot) { // Возврат кредита за победу только в платном режиме (следующий уровень бесплатный) bool isFreeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл"; if (!isFreeMode) { CreditSystem.AddCredit(CreditSystem.BotGamePrice, playerId); } StartBotGame(); } } public void MenuGame(bool endGame = false) { bool isFreeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл"; if (!endGame && !isFreeMode) { if (gameMode == GameMode.PlayerVsPlayer) { int price = CreditSystem.PlayerGamePrice; int half = price / 2; if (CreditSystem.IsSharedBalance()) { // Общий пул — возвращаем полную цену один раз CreditSystem.AddCredit(price, 1); } else if (paidFroFriend) { // За этого игрока платил оппонент — возвращаем деньги оппоненту CreditSystem.AddCredit(price, opponentManager.playerId); } else if (opponentManager.paidFroFriend) { // За оппонента платил этот игрок — возвращаем деньги этому игроку CreditSystem.AddCredit(price, playerId); } else { // Оба платили по половине — возвращаем каждому по половине CreditSystem.AddCredit(half, playerId); CreditSystem.AddCredit(half, opponentManager.playerId); } } else { // Бот: рефанд только если игрок ещё не выиграл ни разу (вышел с первой игры) if (currentBotLevel == 0) CreditSystem.AddCredit(CreditSystem.BotGamePrice, playerId); } } // Единственный вызов — покрывает и endGame=true, и endGame=false, и freeMode if (gameMode == GameMode.PlayerVsPlayer) opponentManager.ReturnToMainMenu(); ReturnToMainMenu(); } public void ClearPole() { if (playerField != null) { playerField.ClearPole(); playerShips = 0; SetPlayerReady(false); Debug.Log("Поле очищено"); } } public void ArrangeGame() { if (playerField != null) { playerField.ArrangeShip(); playerShips = playerField.ShipCount; SetPlayerReady(true); Debug.Log("Поле очищено и корабли расставлены заново"); } else { Debug.LogWarning("GamePole не назначен в GameManager"); } } public void OnPlayVsPlayerClicked(int playerId) // мистр кастыль { bool freeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл"; if (!freeMode) { int opponentId = playerId == 1 ? 2 : 1; int price = CreditSystem.PlayerGamePrice; bool canStart; if (CreditSystem.IsSharedBalance()) { canStart = CreditSystem.GetPlayerCredits(1) >= price; } else { int myCoins = CreditSystem.GetPlayerCredits(playerId); int opponentCoins = CreditSystem.GetPlayerCredits(opponentId); // Проверяем можно ли оплатить в сумме canStart = myCoins + opponentCoins >= price; } if (!canStart) { uiManager?.ShowOverlay("InsufficientFunds", true, hideOtherOverlays: false); return; } } int oppId = playerId == 1 ? 2 : 1; OnInviteSent?.Invoke(playerId, oppId); } public void AcceptInvite() { int price = CreditSystem.PlayerGamePrice; int half = price / 2; if (CreditSystem.IsSharedBalance()) { CreditSystem.Pay(price, 1); } else { int myCredits = CreditSystem.GetPlayerCredits(playerId); int oppCredits = CreditSystem.GetPlayerCredits(opponentManager.playerId); if (myCredits >= half && oppCredits >= (price - half)) { // Я плачу меньшую половину, оппонент — большую (или равную при чётной цене) CreditSystem.Pay(half, playerId); CreditSystem.Pay(price - half, opponentManager.playerId); } else if (oppCredits >= half && myCredits >= (price - half)) { // Оппонент платит меньшую, я — большую CreditSystem.Pay(price - half, playerId); CreditSystem.Pay(half, opponentManager.playerId); } else if (oppCredits >= price) { // Пригласивший покрывает за обоих paidFroFriend = true; CreditSystem.Pay(price, opponentManager.playerId); } else if (myCredits >= price) { // Принимающий покрывает за обоих opponentManager.paidFroFriend = true; CreditSystem.Pay(price, playerId); } else { // Комбинированная оплата: у каждого не хватает на полную цену, // но в сумме хватает. Каждый платит сколько может. int myShare = Mathf.Min(myCredits, price - oppCredits); int oppShare = price - myShare; if (myShare > 0) CreditSystem.Pay(myShare, playerId); if (oppShare > 0) CreditSystem.Pay(oppShare, opponentManager.playerId); } } StartPlayerGame(); opponentManager.StartPlayerGame(); } public void DeclineInvite() { uiManager.HideInvite(); } public void StartPlayerGame() { // PvP всегда использует классический режим 10x10 // Конфигурируем оба поля — это сбрасывает сохранённые рифы (если были в режиме "Рифы") playerField.Configure(10, 10); enemyField.Configure(10, 10); PrepareNewGame(); Debug.Log("Начинаем игру с игроком"); gameMode = GameMode.PlayerVsPlayer; if (playerId == 1) { SoundManager.Instance?.SetPvPMode(true); } SetState(GameState.ShipPlacement); SetInputMode(InputMode.UI); uiManager.ShowOverlay("PlacementChoice", true, hideOtherOverlays: true); SetInputMode(InputMode.UI); fortInitializer.FortSwitch(3); enemyManager.SetEnemyPlayer(); } /// /// Режим игры для этого экземпляра GameManager. /// Если "Выбор режима игроком" включён — устанавливается кнопками StartBotClassic/Growing/Reefs. /// Если выключён — ConfigureFields читает из SettingsManager каждый раз. /// При возврате в меню сбрасывается в значение из SettingsManager. /// private string _effectiveMode = null; /// Вызывается кнопкой «Играть с ботом». /// Если в настройках включён «Выбор режима игроком» — показывает панель выбора, /// иначе сразу запускает игру с режимом из настроек. public void TryStartBotGame() { bool playerChooses = SettingsManager.instance?.GetSettingValue("Выбор режима игроком") == "Вкл"; if (playerChooses) { // Проверяем баланс ДО показа панели, чтобы не открывать её зря bool isFreeMode = SettingsManager.instance?.GetSettingValue("Бесплатная игра") == "Вкл"; if (!isFreeMode) { int price = CreditSystem.GetEffectiveBotPrice(isReplay: false); int credits = CreditSystem.GetPlayerCredits(playerId); if (credits < price) { uiManager?.ShowOverlay("InsufficientFunds", true, hideOtherOverlays: false); return; } } gameMode = GameMode.PlayerVsBot; uiManager.ShowOverlay("GameModeSelect", true, hideOtherOverlays: true); SetInputMode(InputMode.UI); } else { // Баланс проверит сам StartBotGame через PayBotGame StartBotGame(); } } /// Кнопка «Классика» на панели выбора режима. public void StartBotClassic() { _effectiveMode = "Классика"; StartBotGame(); } /// Кнопка «Растущие поля» на панели выбора режима. public void StartBotGrowing() { _effectiveMode = "Растущие поля"; StartBotGame(); } /// Кнопка «Рифы» на панели выбора режима. public void StartBotReefs() { _effectiveMode = "Рифы"; StartBotGame(); } public void StartBotGame(bool isReplay = false) { // Сбрасываем скидку Виндисты — начинается новая игра VindistaController.Instance?.SetDiscountMode(false, playerId); // Если есть оппонент, который ждёт в главном меню - закрываем его приглашение if (opponentManager != null && opponentManager.currentState == GameState.MainMenu) { opponentManager.DeclineInvite(); } // PayBotGame сам считает скидку если isReplay=true и она включена в настройках if (!CreditSystem.PayBotGame(isReplay, playerId)) return; // Конфигурируем поле под текущий режим игры и уровень босса ConfigureFields(); PrepareNewGame(); // Рифы применяем ПОСЛЕ ClearPole (чтобы массивы не были обнулены) ApplyModeReefs(); Debug.Log("Начинаем игру с ботом"); gameMode = GameMode.PlayerVsBot; if (playerId == 1) SoundManager.Instance?.SetPvPMode(false); SetState(GameState.ShipPlacement); SetInputMode(InputMode.UI); // Затем показать overlay выбора расстановки uiManager.ShowOverlay("PlacementChoice", true, hideOtherOverlays: true); InitBot(); } //========================================================================================== /// /// Читает режим игры из настроек и конфигурирует оба поля (размер + кол-во кораблей). /// Вызывается перед PrepareNewGame() в StartBotGame. /// private void ConfigureFields() { bool playerChooses = SettingsManager.instance?.GetSettingValue("Выбор режима игроком") == "Вкл"; string mode; if (playerChooses) { // Режим выбран игроком и записан в _effectiveMode кнопками StartBotClassic/Growing/Reefs. // При реванше/продолжении _effectiveMode уже содержит режим этого экземпляра — не трогаем чужой синглтон. mode = _effectiveMode ?? SettingsManager.instance?.GetSettingValue("Режим игры") ?? "Классика"; } else { // Режим задан администратором в настройках — читаем каждый раз из SettingsManager. mode = SettingsManager.instance?.GetSettingValue("Режим игры") ?? "Классика"; } _effectiveMode = mode; // сохраняем для ApplyModeReefs и следующего реванша int fieldSize = 10; int shipCount = 10; if (mode != "Классика" && bossLevelConfig != null) { var data = bossLevelConfig.GetLevel(currentBotLevel); if (mode == "Растущие поля") { fieldSize = data.fieldSize; shipCount = data.shipCountGame1; } else if (mode == "Рифы") { fieldSize = 10; shipCount = data.shipCountGame2; } } playerField.Configure(fieldSize, shipCount); enemyField.Configure(fieldSize, shipCount); Debug.Log($"[GameManager] Режим игры: '{mode}', поле: {fieldSize}x{fieldSize}, кораблей: {shipCount}"); } private void ApplyModeReefs() { if (bossLevelConfig == null) return; if (_effectiveMode != "Рифы") return; var data = bossLevelConfig.GetLevel(currentBotLevel); playerField.ApplyRandomReefs(data.reefs); enemyField.ApplyRandomReefs(data.reefs); Debug.Log($"[GameManager] Рифы применены для уровня {currentBotLevel}"); } private void InitBot() { BotDifficulty selectedDifficulty = GetDifficultyFromLevel(); Debug.Log("Выбран бот: " + selectedDifficulty); switch (selectedDifficulty) { case BotDifficulty.Level1: bot = new EasyBotAI(); enemyManager.SetEnemy(0); fortInitializer.FortSwitch(0); break; case BotDifficulty.Level2: bot = new NormalBotAI(); enemyManager.SetEnemy(1); fortInitializer.FortSwitch(1); break; case BotDifficulty.Level3: bot = new HardBotAI(); enemyManager.SetEnemy(2); fortInitializer.FortSwitch(2); break; case BotDifficulty.Level4: bot = new VeryHardBotAI(); enemyManager.SetEnemy(3); fortInitializer.FortSwitch(3); break; case BotDifficulty.Level5: bot = new CrazyBotAI(); enemyManager.SetEnemy(4); fortInitializer.FortSwitch(4); break; case BotDifficulty.Level6: bot = new UltimateBotAI(); enemyManager.SetEnemy(4); fortInitializer.FortSwitch(4); break; default: bot = new EasyBotAI(); enemyManager.SetEnemy(0); fortInitializer.FortSwitch(0); break; } } // ходы public void StartPlayerTurn() { isPlayerTurn = true; uiManager.ShowTimer(true); if (targetCursorInstance != null) targetCursorInstance.SetActive(true); } public void EndPlayerTurn() { isPlayerTurn = false; uiManager.ShowTimer(false); if (targetCursorInstance != null) targetCursorInstance.SetActive(false); if (gameMode == GameMode.PlayerVsPlayer) { opponentManager.StartPlayerTurn(); } else { // enemyManager.ShowNextDialogue(); StartCoroutine(BotTurn()); } } public IEnumerator BotTurn() { bool hit; do { yield return new WaitForSeconds(1f); Vector2Int target = bot.MakeMove(playerField); if (target.x < 0 || target.y < 0) { Debug.LogWarning("Боту нечего стрелять, пропускаем ход"); break; // или EndGame, если нет ходов } bool shotComplete = false; hit = false; enemyBattery.FireShot(playerField, target.x, target.y, (wasHit, wasShipDestroyed) => { hit = wasHit; shotComplete = true; ProcessShotResult(wasHit,false, wasShipDestroyed); // if (hit) SoundManager.Instance.PlayBotHit(playerId); // else SoundManager.Instance.PlayBotMiss(playerId); // if(wasShipDestroyed) SoundManager.Instance.PlayPlayerShipDestroyed(playerId); }); yield return new WaitUntil(() => shotComplete); } while (hit); // Не восстанавливаем ход игрока, если игра уже завершена (EndGame был вызван из колбэка выстрела) if (!isGameEnded) { // Если пауза активна — ждём её снятия, иначе StartPlayerTurn() перезатрёт InputMode.UI if (battlePauseShown) yield return new WaitUntil(() => !battlePauseShown); StartPlayerTurn(); } } /// /// Сбрасывает игровое состояние - очищает поля, удаляет курсор, сбрасывает переменные /// private void ResetGameState() { Debug.Log("Сброс состояния игры..."); // 1. Сбрасываем игровые поля if (playerField != null) { playerField.ClearPole(); // очистка своего поля (удаляем корабли и сбрасываем массивы) } if (enemyField != null) { enemyField.ClearPole(); // очистка вражеского поля } // 2. Удаляем курсор if (targetCursorInstance != null) { Destroy(targetCursorInstance); targetCursorInstance = null; } // 3. Сбрасываем переменные playerShips = 0; enemyShips = 0; SetPlayerReady(false); isPlayerTurn = false; SetInputMode(InputMode.UI); autoStartBattlePending = false; shipPlacementTimer?.StopTimer(); Debug.Log("Сброс завершён"); } /// /// Подготовка к новой игре - сбрасывает состояние и переводит в состояние расстановки /// private void PrepareNewGame() { isGameEnded = false; ResetGameState(); // Состояние будет установлено в вызывающем методе (StartPlayerGame или StartBotGame) } /// /// Возврат в главное меню - сбрасывает состояние и показывает главное меню /// public void ReturnToMainMenu() { // Сбрасываем скидку Виндисты VindistaController.Instance?.SetDiscountMode(false, playerId); paidFroFriend = false; currentBotLevel = 0; _effectiveMode = null; // сброс: следующая игра возьмёт режим из SettingsManager заново ResetGameState(); if (playerId == 1) SoundManager.Instance?.SetPvPMode(false); idleVideoRequested = false; SetState(GameState.MainMenu); } private void HandleShipPlacementTimeout() { autoStartBattlePending = true; uiManager.HideAllOverlays(); if (playerShips != playerField.ShipCount) { playerField.ArrangeShip(); playerShips = playerField.ShipCount; } SetPlayerReady(true); TryStartBattleAfterPlacementTimeout(); } private void TryStartBattleAfterPlacementTimeout() { if (!autoStartBattlePending || currentState != GameState.ShipPlacement || !isReady) return; if (gameMode == GameMode.PlayerVsPlayer) { if (opponentManager == null || !opponentManager.isReady) return; autoStartBattlePending = false; opponentManager.autoStartBattlePending = false; StartBattle(); return; } autoStartBattlePending = false; StartBattle(); } }