1196 lines
40 KiB
C#
1196 lines
40 KiB
C#
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 Transform shipPlacementCameraPosition;
|
||
public Transform mainMenuCameraPosition;
|
||
public Transform gameplayCameraPosition;
|
||
|
||
[Tooltip("Центр вращения камеры во время расстановки кораблей (пустой GO у поля игрока)")]
|
||
[SerializeField] private Transform shipPlacementPivotPoint;
|
||
/// <summary>Исходная мировая позиция cameraPivot — восстанавливается вне режима расстановки.</summary>
|
||
private Vector3 _defaultPivotPosition;
|
||
|
||
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 Transform cameraPivot;
|
||
|
||
public int currentBotLevel = 0;
|
||
|
||
public EnemyManager enemyManager;
|
||
public FortInitializer fortInitializer;
|
||
|
||
|
||
[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;
|
||
|
||
|
||
public float cameraMoveDuration = 1.0f; // Время перемещения в секундах
|
||
|
||
void Start()
|
||
{
|
||
// Запоминаем исходную позицию пивота (центр карты)
|
||
if (cameraPivot != null)
|
||
_defaultPivotPosition = cameraPivot.position;
|
||
|
||
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);
|
||
// Восстанавливаем режим ввода: если ход игрока — Battle, иначе UI
|
||
SetInputMode(isPlayerTurn ? InputMode.Battle : 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:
|
||
MoveCameraTo(mainMenuCameraPosition);
|
||
break;
|
||
|
||
case GameState.ShipPlacement:
|
||
MoveCameraTo(shipPlacementCameraPosition);
|
||
break;
|
||
|
||
case GameState.Playing:
|
||
MoveCameraTo(gameplayCameraPosition);
|
||
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);
|
||
}
|
||
|
||
StartPlayerTurn();
|
||
}
|
||
|
||
|
||
|
||
public void InitTargetCursor(bool isPlacingShips = false)
|
||
{
|
||
if (targetCursorInstance != null)
|
||
return;
|
||
targetCursorInstance = Instantiate(targetCursorPrefab);
|
||
TargetCursor cursorScript = targetCursorInstance.GetComponent<TargetCursor>();
|
||
cursorScript.Initialize(this, 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
|
||
{
|
||
int price = CreditSystem.PlayerGamePrice;
|
||
if (CreditSystem.IsSharedBalance())
|
||
{
|
||
// Общий пул — платим ровно цену один раз
|
||
CreditSystem.Pay(price, 1);
|
||
}
|
||
else if (CreditSystem.GetPlayerCredits(opponentManager.playerId) >= price)
|
||
{
|
||
CreditSystem.Pay(price, playerId);
|
||
CreditSystem.Pay(price, opponentManager.playerId);
|
||
}
|
||
else
|
||
{
|
||
paidFroFriend = true;
|
||
CreditSystem.Pay(price * 2, 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;
|
||
if (CreditSystem.IsSharedBalance())
|
||
{
|
||
// Общий пул — возвращаем ровно цену один раз
|
||
CreditSystem.AddCredit(price, 1);
|
||
}
|
||
else if (paidFroFriend) CreditSystem.AddCredit(price * 2, playerId);
|
||
else if (opponentManager.paidFroFriend) CreditSystem.AddCredit(price * 2, opponentManager.playerId);
|
||
else
|
||
{
|
||
CreditSystem.AddCredit(price, playerId);
|
||
CreditSystem.AddCredit(price, opponentManager.playerId);
|
||
}
|
||
opponentManager.ReturnToMainMenu();
|
||
}
|
||
else
|
||
{
|
||
// Бот: рефанд только если игрок ещё не выиграл ни разу (вышел с первой игры)
|
||
if (currentBotLevel == 0) CreditSystem.AddCredit(CreditSystem.BotGamePrice, playerId);
|
||
}
|
||
}
|
||
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 >= price && opponentCoins >= price) || myCoins >= price * 2;
|
||
}
|
||
|
||
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;
|
||
if (CreditSystem.IsSharedBalance())
|
||
{
|
||
// Общий пул — платим ровно цену один раз
|
||
CreditSystem.Pay(price, 1);
|
||
}
|
||
else if (CreditSystem.GetPlayerCredits(playerId) >= price)
|
||
{
|
||
CreditSystem.Pay(price, playerId);
|
||
CreditSystem.Pay(price, opponentManager.playerId);
|
||
}
|
||
else
|
||
{
|
||
opponentManager.paidFroFriend = true;
|
||
CreditSystem.Pay(price * 2, 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();
|
||
}
|
||
|
||
private string _modeOverride = null;
|
||
/// <summary>Режим, применённый в текущей игре (определяется в ConfigureFields).</summary>
|
||
private string _effectiveMode = "Классика";
|
||
|
||
/// <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;
|
||
}
|
||
}
|
||
|
||
uiManager.ShowOverlay("GameModeSelect", true, hideOtherOverlays: true);
|
||
SetInputMode(InputMode.UI);
|
||
}
|
||
else
|
||
{
|
||
// Баланс проверит сам StartBotGame через PayBotGame
|
||
StartBotGame();
|
||
}
|
||
}
|
||
|
||
/// <summary>Кнопка «Классика» на панели выбора режима.</summary>
|
||
public void StartBotClassic()
|
||
{
|
||
_modeOverride = "Классика";
|
||
StartBotGame();
|
||
}
|
||
|
||
/// <summary>Кнопка «Растущие поля» на панели выбора режима.</summary>
|
||
public void StartBotGrowing()
|
||
{
|
||
_modeOverride = "Растущие поля";
|
||
StartBotGame();
|
||
}
|
||
|
||
/// <summary>Кнопка «Рифы» на панели выбора режима.</summary>
|
||
public void StartBotReefs()
|
||
{
|
||
_modeOverride = "Рифы";
|
||
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()
|
||
{
|
||
string mode = _modeOverride ?? SettingsManager.instance?.GetSettingValue("Режим игры") ?? "Классика";
|
||
_modeOverride = null; // сбрасываем override
|
||
_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);
|
||
SetInputMode(InputMode.Battle);
|
||
}
|
||
|
||
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();
|
||
}
|
||
}
|
||
|
||
|
||
public void MoveCameraTo(Transform target)
|
||
{
|
||
if (playerCamera != null && target != null)
|
||
{
|
||
StopAllCoroutines();
|
||
StartCoroutine(MoveCameraSmoothly(target));
|
||
}
|
||
}
|
||
|
||
|
||
private IEnumerator MoveCameraSmoothly(Transform target)
|
||
{
|
||
Transform cam = playerCamera.transform;
|
||
|
||
// Отсоединяем камеру от пивота на время перемещения —
|
||
// иначе поворот пивота кнопками во время полёта камеры сдвигает её конечную позицию
|
||
cam.SetParent(null);
|
||
|
||
Vector3 startPos = cam.position;
|
||
Quaternion startRot = cam.rotation;
|
||
|
||
Vector3 endPos = target.position;
|
||
Quaternion endRot = target.rotation;
|
||
|
||
float elapsed = 0f;
|
||
|
||
while (elapsed < cameraMoveDuration)
|
||
{
|
||
float t = elapsed / cameraMoveDuration;
|
||
cam.position = Vector3.Lerp(startPos, endPos, t);
|
||
cam.rotation = Quaternion.Slerp(startRot, endRot, t);
|
||
elapsed += Time.deltaTime;
|
||
yield return null;
|
||
}
|
||
|
||
cam.position = endPos;
|
||
cam.rotation = endRot;
|
||
|
||
// Сбрасываем пивот и возвращаем камеру обратно как дочерний объект
|
||
if (cameraPivot != null)
|
||
{
|
||
cameraPivot.rotation = Quaternion.identity;
|
||
|
||
// Перемещаем пивот: во время расстановки кораблей — к полю игрока,
|
||
// в остальных состояниях — обратно на дефолтную позицию
|
||
bool isShipPlacement = (target == shipPlacementCameraPosition);
|
||
if (isShipPlacement && shipPlacementPivotPoint != null)
|
||
cameraPivot.position = shipPlacementPivotPoint.position;
|
||
else
|
||
cameraPivot.position = _defaultPivotPosition;
|
||
|
||
// worldPositionStays: true — локальные координаты пересчитаются под новый пивот
|
||
cam.SetParent(cameraPivot, true);
|
||
|
||
// Фиксируем новый "дом" и синхронизируем квадрант курсора
|
||
var camCtrl = cameraPivot.GetComponent<CameraPivotController>();
|
||
if (camCtrl != null)
|
||
{
|
||
camCtrl.CaptureHomeNow();
|
||
camCtrl.TargetCursor?.SwitchButton(cameraPivot.rotation.eulerAngles);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <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;
|
||
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();
|
||
}
|
||
}
|