sea-battle/Assets/Scripts/GameManager.cs

1003 lines
30 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 Transform shipPlacementCameraPosition;
public Transform mainMenuCameraPosition;
public Transform gameplayCameraPosition;
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;
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;
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()
{
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)
{
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 == 10)
{
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)
{
Debug.Log("Шанс на победу сработал!");
DifficultyManager.ResetChance();
return BotDifficulty.Level5; // даем шанс победить
}
else
{
DifficultyManager.DecreaseChance();
return BotDifficulty.Level6; // почти непобедимый
}
}
return (BotDifficulty)level;
}
public void PrepareForBattle()
{
if (playerShips != 10)
{
playerField.ArrangeShip();
playerShips = 10;
}
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 = 10;
}
if (!opponentManager.enemyField.IsReady)
{
opponentManager.enemyField.CopyShipsFrom(playerField, true);
opponentManager.enemyShips = 10;
}
paidFroFriend = false;
opponentManager.paidFroFriend = false;
opponentManager.uiManager.ShowTimer(false);
isReady = false;
opponentManager.isReady = false;
}
else // бот
{
PrepareForBattle();
enemyShips = 10;
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()
{
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);
uiManager.ShowGameOverMenu(isWin, isBotGame);
if (gameMode == GameMode.PlayerVsBot)
{
if (isWin)
{
Prize priz = PrizeSerializer.GetPrizes()[currentBotLevel];
Debug.Log("Приз :"+ priz);
// Повышаем уровень бота ПЕРЕД выдачей приза
if (currentBotLevel <= 4)
currentBotLevel++;
else
DifficultyManager.DecreaseChance();
// Задержка перед показом панели призов
yield return new WaitForSeconds(1.5f);
if (priz.PrizeType == PrizeType.Special)
{
bool settingEnabled = SettingsManager.instance.GetSettingValue("Выдача призов при игре с ботом") == "Выдавать";
if (settingEnabled)
{
uiManager.ShowOverlayAnimated("QRCode", 0f);
yield break;
}
}
if (priz.PrizeType == PrizeType.Showcase)
{
bool settingEnabled = SettingsManager.instance.GetSettingValue("Выдача призов при игре с ботом") == "Выдавать";
if (settingEnabled)
{
uiManager.ShowOverlayAnimated("PrizeSelection", 0f);
yield break;
}
}
if (priz.PrizeType == PrizeType.None) yield break;
uiManager.ShowOverlayAnimated("PrizeReminder", 0f);
PrizeSystem.GivePrize(priz, playerId);
}
}
}
// ============= buttons ==========================================================================
public void OnReplayPressed() // по идеее функции разные так как нужна проверка на баланс $
{
if (gameMode == GameMode.PlayerVsBot)
{
StartBotGame();
}
else
{
if (CreditSystem.GetPlayerCredits(opponentManager.playerId) > 0)
{
CreditSystem.Pay(1, playerId);
CreditSystem.Pay(1, opponentManager.playerId);
}
else
{
paidFroFriend = true;
CreditSystem.Pay(2, playerId);
}
opponentManager.StartPlayerGame();
StartPlayerGame();
}
}
public void OnContinuePressed()
{
if (gameMode == GameMode.PlayerVsBot)
{
// Возврат кредита за победу только в платном режиме
bool isFreeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл";
if (!isFreeMode)
{
CreditSystem.AddCredit(1, playerId);
}
StartBotGame();
}
}
public void MenuGame(bool endGame = false)
{
// Возврат кредитов при выходе (если не завершилась игра)
// НЕ возвращаем если включена бесплатная игра
bool isFreeMode = SettingsManager.instance.GetSettingValue("Бесплатная игра") == "Вкл";
if (!endGame && !isFreeMode)
{
if (gameMode == GameMode.PlayerVsPlayer)
{
if (paidFroFriend) CreditSystem.AddCredit(2, playerId);
else if(opponentManager.paidFroFriend) CreditSystem.AddCredit(2, opponentManager.playerId);
else
{
CreditSystem.AddCredit(1, playerId);
CreditSystem.AddCredit(1, opponentManager.playerId);
}
opponentManager.ReturnToMainMenu();
}
else
{
if(currentBotLevel == 0) CreditSystem.AddCredit(1, 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 = 10;
SetPlayerReady(true);
Debug.Log("Поле очищено и корабли расставлены заново");
}
else
{
Debug.LogWarning("GamePole не назначен в GameManager");
}
}
public void OnPlayVsPlayerClicked(int playerId) // мистр кастыль
{
int opponentId = playerId == 1 ? 2 : 1;
OnInviteSent?.Invoke(playerId, opponentId);
}
public void AcceptInvite()
{
if (CreditSystem.GetPlayerCredits(playerId) > 0)
{
CreditSystem.Pay(1, playerId);
CreditSystem.Pay(1, opponentManager.playerId);
}
else
{
opponentManager.paidFroFriend = true;
CreditSystem.Pay(2, opponentManager.playerId);
}
StartPlayerGame();
opponentManager.StartPlayerGame();
}
public void DeclineInvite()
{
uiManager.HideInvite();
}
public void StartPlayerGame()
{
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();
}
public void StartBotGame()
{
// Если есть оппонент, который ждёт в главном меню - закрываем его приглашение
if (opponentManager != null &&
opponentManager.currentState == GameState.MainMenu)
{
opponentManager.DeclineInvite();
}
CreditSystem.Pay(1, playerId);
PrepareNewGame();
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();
}
//==========================================================================================
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);
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;
// 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()
{
ResetGameState();
// Состояние будет установлено в вызывающем методе (StartPlayerGame или StartBotGame)
}
/// <summary>
/// Возврат в главное меню - сбрасывает состояние и показывает главное меню
/// </summary>
public void ReturnToMainMenu()
{
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 != 10)
{
playerField.ArrangeShip();
playerShips = 10;
}
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();
}
}