514 lines
16 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 System.Text;
using UnityEngine;
using UnityEngine.Events;
using System.Threading.Tasks;
using System.Threading;
public class ComWrapper : MonoBehaviour
{
//Настраиваем из инспектора
[SerializeField] bool saveMoneyBetweenSessions;
[SerializeField] bool enablePrizeChecking = false; // Если хопер с проверкой наличия приза ставим true
[SerializeField] bool keepAlive = true;
public static UnityEvent onPulseRecieved;
public static UnityEvent<int> onCoinAdded;
public static UnityEvent<int> onCoinRemoved;
public static UnityEvent<string> onInputRecieved;
private const string REQUEST_INPUT = "91";
private static int h;//идентификатор хэндла
private static byte[] buf = new byte[1024]; //Байт-буфер куда будут приходить данные с порта
public static int Balance;
bool boardEncrypted = false;
string encString;
string confirmString;
Coroutine keepAliveCoroutine;
//async
private CancellationTokenSource pollCancellationTokenSource;
//===========================start: PUBLIC API===========================//
public void Initialize()
{
//добавить проверку флага инициализации
//Создаем ивенты
onCoinAdded = new UnityEvent<int>();
onCoinRemoved = new UnityEvent<int>();
onInputRecieved = new UnityEvent<string>();
onPulseRecieved = new UnityEvent();
//Начальная настройка
if (!saveMoneyBetweenSessions) PlayerPrefs.SetInt("CoinsCount", 0);
Balance = PlayerPrefs.GetInt("CoinsCount", 0);
onInputRecieved?.AddListener(InputRecievedCather);
StartCoroutine(SetupBoard());
}
private void send_port_pin_message(int pin)
{
}
public static void AddCredit()
{
int recieved = PlayerPrefs.GetInt("ValutaPerImpulse", 10);
Balance = Balance + recieved;
PlayerPrefs.SetInt("CoinsCount", Balance);
//Счетчики выручки
PlayerPrefs.SetInt(PrizeSystem.key_starts, PlayerPrefs.GetInt(PrizeSystem.key_starts) + recieved);
PlayerPrefs.SetInt(PrizeSystem.key_startsNul, PlayerPrefs.GetInt(PrizeSystem.key_startsNul) + recieved);
if (SettingsManager.GetString("BaseBotDifficulty", "Фиксированная") == "Повышенная")
{
int nowPulses = PlayerPrefs.GetInt("PulsesCounter", 0) + 1;
PlayerPrefs.SetInt("PulsesCounter", nowPulses);
}
onPulseRecieved?.Invoke();
onCoinAdded?.Invoke(Balance);
}
public static void ReturnCredits(GameMode gameMode)
{
string gamemodePriceKey = gameMode == GameMode.pvp ? "PricePVP" : "PricePVE";
int gamemodePrice = PlayerPrefs.GetInt(gamemodePriceKey, 10);
Balance += gamemodePrice;
onCoinAdded?.Invoke(Balance);
}
public static void Send(string message)
{
try
{
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1); //w-количество записаных символов в порт. можно обработать
//Debug.Log("Wrote: " + w);
}
catch { Debug.LogError("Ошибка отправки строкового сигнала."); }
}
public static void Send(LightMode lightMode)
{
try
{
string signal = LightUtility.GetLightString(lightMode);
Send(signal);
}
catch { Debug.LogError("Ошибка отправки светового сигнала."); }
}
public static bool Pay(int amount)
{
//блок проверки оплаты
if (SettingsManager.GetBool("Freeplay", false))
{
return true;
}
if (Balance >= amount)
{
Balance -= amount;
PlayerPrefs.SetInt("CoinsCount", Balance);
onCoinRemoved?.Invoke(Balance);
return true;
}
return false;
}
//===========================end: PUBLIC API===========================//
private void OnDisable()
{
StopAsyncPolling();
ClosePort();
}
private void OnApplicationQuit()
{
StopAsyncPolling();
ClosePort();
}
private void StopAsyncPolling()
{
pollCancellationTokenSource?.Cancel();
pollCancellationTokenSource?.Dispose();
pollCancellationTokenSource = null;
}
private async void StartAsyncPolling()
{
try
{
Logger.Instance.Log("Запуск асинхронного опроса порта");
while (pollCancellationTokenSource != null &&
!pollCancellationTokenSource.Token.IsCancellationRequested)
{
await Task.Delay(10, pollCancellationTokenSource.Token);
string comInput = await ReadBufferLineAsync(pollCancellationTokenSource.Token);
if (!string.IsNullOrEmpty(comInput))
{
// Прямой вызов - мы уже в главном потоке после await
ProcessComInputImmediate(comInput);
}
}
}
catch (OperationCanceledException)
{
Logger.Instance.Log("Асинхронный опрос порта остановлен");
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка асинхронного опроса: {e.Message}");
}
}
private void ProcessComInputImmediate(string comInput)
{
Debug.LogError("COM: " + comInput);
if (comInput == "COIN") AddCredit();
if (comInput.StartsWith("INPUT")) onInputRecieved?.Invoke(comInput.Substring(5));
if (comInput.StartsWith("ENC:")) encString = comInput;
if (comInput.StartsWith("ACTIVATED:")) confirmString = comInput;
}
private void ToggleInputRecieved(int value) => PlayerPrefs.SetInt("inputToggle", value);
private int GetInputToggle() => PlayerPrefs.GetInt("inputToggle", 0);
private void InputRecievedCather(string input)
{
if (!string.IsNullOrEmpty(input))
{
//пришел инпут
ToggleInputRecieved(1);
}
}
#region Приватные методы
private void initPort()
{
Logger.Instance.Log("Инициализация порта");
ComNativeWrapper.Serial_CloseAll();
//ComNativeWrapper.Serial_Close(h);
var ports = ComNativeWrapper.ListPorts();
if (ports.Length == 0) { Debug.LogError("Нет портов, завершаю"); return; }
string port = ports[^1];
Logger.Instance.Log("Открываю порт: " + port);
h = ComNativeWrapper.Serial_Open(port, 115200, 8, 0, 1, 4, 10);
Logger.Instance.Log($"handle: {h}");
if (h <= 0)
{
Logger.Instance.Log("Open failed: " + ComNativeWrapper.GetLastError());
return;
}
else
{
Logger.Instance.Log($"Открыл порт: {port}");
}
}
private static void ClosePort()
{
Logger.Instance.Log($"Отпускаю порт handle:{h}");
if (h > 0)
{
ComNativeWrapper.Serial_Close(h);
h = 0;
}
}
private static string ReadBufferLine()
{
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
if (read > 0)
{
var s = Encoding.UTF8.GetString(buf, 0, read);
//DebugWindow.Message($"ReadLine len={read} data=[{s}]");
return s;
}
else if (read < 0)
{
//DebugWindow.Message($"ReadLine err={read} :: {ComNativeWrapper.GetLastError()}");
}
return "";
}
private static async Task<string> ReadBufferLineAsync(CancellationToken cancellationToken = default)
{
try
{
return await Task.Run(() =>
{
// Проверяем отмену внутри Task.Run
if (cancellationToken.IsCancellationRequested)
return string.Empty;
int read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, buf, buf.Length);
if (read > 0)
{
return Encoding.UTF8.GetString(buf, 0, read);
}
return string.Empty;
}, cancellationToken);
}
catch (OperationCanceledException)
{
Logger.Instance.Log("Чтение порта отменено");
return string.Empty;
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка асинхронного чтения: {e.Message}");
return string.Empty;
}
}
private void requestInputs() => Send(REQUEST_INPUT);
private void pollPort()
{
//Тут все просто, читаем что нам приходит с порта и сравниваем.
string comInput = ReadBufferLine();
if (comInput != "")
{
Debug.Log("COM: " + comInput);
if (comInput == "COIN") AddCredit();
if (comInput.StartsWith("INPUT")) onInputRecieved?.Invoke(comInput.Substring(5)); //инвокаю сразу без префикса "INPUT"
if (comInput.StartsWith("ENC:")) encString = comInput;
if (comInput.StartsWith("ACTIVATED:")) confirmString = comInput;
}
}
private IEnumerator SetupBoard()
{
if (keepAliveCoroutine != null) StopCoroutine(keepAliveCoroutine);
initPort();
boardEncrypted = true; //предполагаем что зашифрована
//пробуем получить инпуты. Вдруг плата осталась расшифрована
ToggleInputRecieved(0);
requestInputs();
float timer = 1;
while (GetInputToggle() == 0 && timer > 0)
{
pollPort();
timer -= Time.deltaTime;
yield return null;
}
//Есть инпут. незашифровано
if (GetInputToggle() == 1)
{
boardEncrypted = false;
}
if (boardEncrypted)
{
Logger.Instance.Log("Шифрованная плата");
//запрос
encString = "";
Send("666");
//ждем пока придет ENC:
timer = 1;
while (string.IsNullOrEmpty(encString) && timer > 0)
{
pollPort();
timer -= Time.deltaTime;
yield return null;
}
//получили ENC: строку или вышли по таймеру
//Строка пришла
if (!string.IsNullOrEmpty(encString))
{
Logger.Instance.Log($"Расшифровываю {encString}");
string decr = BoardUtility.DecryptSerial(ExtractData(encString));
//подтверждение
confirmString = "";
Send("777" + decr);
}
else
{
Logger.Instance.Log($"Зашифрованная строка не пришла"); //отвал игры пускать
(new GameObject()).AddComponent(typeof(VfxPrefab));
}
//ждем подтверждение от платы
timer = 2;
while (string.IsNullOrEmpty(confirmString) && timer > 0)
{
pollPort();
timer -= Time.deltaTime;
yield return null;
}
if (string.IsNullOrEmpty(confirmString))
{
Application.Quit();
}
else
{
if (confirmString.IndexOf("OK") != -1)
{
Logger.Instance.Log("Активировано.");
}
else
{
Application.Quit();
}
}
}
else
{
Logger.Instance.Log("НЕшифрованная плата");
}
//async
//Каждые 5 сек запрашиваем инпуты у платы. Надо потому что при заходе в игру с пинов еще не пришла инфа
/* if (keepAlive)
keepAliveCoroutine = StartCoroutine(KeepPortAliveProcess());*/
//async
pollCancellationTokenSource = new CancellationTokenSource();
StartAsyncPolling();
StartKeepAliveProcess();
}
private async void StartKeepAliveProcess()
{
if (keepAlive)
{
await StartKeepAliveProcessAsync(pollCancellationTokenSource.Token);
}
}
private async Task StartKeepAliveProcessAsync(CancellationToken cancellationToken)
{
try
{
Logger.Instance.Log("Запуск асинхронной проверки порта");
while (!cancellationToken.IsCancellationRequested)
{
await SendRequestInputsAsync(cancellationToken);
await Task.Delay(2000, cancellationToken); // 2 секунды
if (!await CheckPortConnectionAsync(cancellationToken))
{
Logger.Instance.Log("Плата отвалилась. Запускаю переподключение.");
await ReinitializePortAsync(cancellationToken);
}
else
{
ToggleInputRecieved(0);
}
await Task.Delay(10000, cancellationToken); // 10 секунд
}
}
catch (OperationCanceledException)
{
Logger.Instance.Log("Асинхронная проверка порта остановлена");
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка проверки порта: {e.Message}");
}
}
private async Task SendRequestInputsAsync(CancellationToken cancellationToken)
{
try
{
requestInputs();
await Task.CompletedTask;
}
catch (Exception e)
{
Logger.Instance.Log($"Ошибка отправки запроса инпутов: {e.Message}");
}
}
private async Task<bool> CheckPortConnectionAsync(CancellationToken cancellationToken)
{
// Простая проверка, не требует Task.Run
return await Task.FromResult(GetInputToggle() != 0);
}
private async Task ReinitializePortAsync(CancellationToken cancellationToken)
{
await Task.Run(() => initPort(), cancellationToken);
}
private static string ExtractData(string input)
{
const string encPrefix = "ENC:";
int encIndex = input.IndexOf(encPrefix);
if (encIndex == -1)
{
Logger.Instance.Log($"Неверный формат шифрованной строки {input}");
throw new ArgumentException("Invalid input format");
}
int dataStart = encIndex + encPrefix.Length;
return input.Substring(4);
}
#endregion
}
public enum LightMode
{
Idle1,
Idle2,
Orange,
Blue,
Win,
Lose,
Prize,
Toaster,
Tama,
Dog,
Sam,
Mistress
}