sea-battle/Assets/Scripts/GameManager.cs
2026-05-15 13:02:11 +03:00

1241 lines
42 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static event System.Action<int, int> OnInviteSent;
public static event System.Action <int, bool, GameMode> OnReady;
public event System.Action<InputMode> 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;
/// <summary>True после EndGame() — блокирует таймер паузы и вызов StartPlayerTurn() из BotTurn().</summary>
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<Player>();
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);
}
// ===== Панель правил =====
/// <summary>
/// Открыть панель правил. Вешайте прямо на onClick кнопки «Правила» в Inspector.
/// </summary>
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<TargetCursor>();
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();
}
/// <summary>
/// Режим игры для этого экземпляра GameManager.
/// Если "Выбор режима игроком" включён — устанавливается кнопками StartBotClassic/Growing/Reefs.
/// Если выключён — ConfigureFields читает из SettingsManager каждый раз.
/// При возврате в меню сбрасывается в значение из SettingsManager.
/// </summary>
private string _effectiveMode = null;
/// <summary>Вызывается кнопкой «Играть с ботом».
/// Если в настройках включён «Выбор режима игроком» — показывает панель выбора,
/// иначе сразу запускает игру с режимом из настроек.</summary>
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();
}
}
/// <summary>Кнопка «Классика» на панели выбора режима.</summary>
public void StartBotClassic()
{
_effectiveMode = "Классика";
StartBotGame();
}
/// <summary>Кнопка «Растущие поля» на панели выбора режима.</summary>
public void StartBotGrowing()
{
_effectiveMode = "Растущие поля";
StartBotGame();
}
/// <summary>Кнопка «Рифы» на панели выбора режима.</summary>
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();
}
//==========================================================================================
/// <summary>
/// Читает режим игры из настроек и конфигурирует оба поля (размер + кол-во кораблей).
/// Вызывается перед PrepareNewGame() в StartBotGame.
/// </summary>
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();
}
}
/// <summary>
/// Сбрасывает игровое состояние - очищает поля, удаляет курсор, сбрасывает переменные
/// </summary>
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("Сброс завершён");
}
/// <summary>
/// Подготовка к новой игре - сбрасывает состояние и переводит в состояние расстановки
/// </summary>
private void PrepareNewGame()
{
isGameEnded = false;
ResetGameState();
// Состояние будет установлено в вызывающем методе (StartPlayerGame или StartBotGame)
}
/// <summary>
/// Возврат в главное меню - сбрасывает состояние и показывает главное меню
/// </summary>
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();
}
}