Унифицировать работу с контроллером и улучшить загрузку персонажей
This commit is contained in:
parent
ca42cf53ba
commit
70c4dbd081
@ -207,7 +207,7 @@ namespace CharacterSystem
|
||||
|
||||
try
|
||||
{
|
||||
await PrepareWindowAsync(newIndex, oldIndex);
|
||||
await PrepareTargetAsync(newIndex, oldIndex);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@ -296,6 +296,47 @@ namespace CharacterSystem
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PrepareTargetAsync(int centerIndex, int additionalIndex)
|
||||
{
|
||||
int generation = ++_loadGeneration;
|
||||
SetDesiredWindow(centerIndex, additionalIndex);
|
||||
|
||||
Task<Character> characterTask = EnsureCharacterLoadedAsync(centerIndex);
|
||||
Task<VideoClip> videoTask = EnsureVideoLoadedAsync(centerIndex);
|
||||
_ = PreloadWindowRemainderAsync(centerIndex, generation);
|
||||
|
||||
await Task.WhenAll(characterTask, videoTask);
|
||||
}
|
||||
|
||||
private async Task PreloadWindowRemainderAsync(int centerIndex, int generation)
|
||||
{
|
||||
int[] indicesToLoad = _desiredIndices
|
||||
.Where(index => index != centerIndex)
|
||||
.ToArray();
|
||||
List<Task> loadingTasks = new();
|
||||
|
||||
foreach (int characterIndex in indicesToLoad)
|
||||
{
|
||||
loadingTasks.Add(EnsureCharacterLoadedAsync(characterIndex));
|
||||
loadingTasks.Add(EnsureVideoLoadedAsync(characterIndex));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(loadingTasks);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Debug.LogException(exception, this);
|
||||
}
|
||||
|
||||
if (generation == _loadGeneration)
|
||||
{
|
||||
ReleaseUndesiredCharacters();
|
||||
ReleaseUndesiredVideos();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Character> EnsureCharacterLoadedAsync(int characterIndex)
|
||||
{
|
||||
if (_loadedCharacters.TryGetValue(characterIndex, out LoadedCharacter loadedCharacter))
|
||||
|
||||
@ -117,21 +117,12 @@ public class Bootstrap : MonoBehaviour
|
||||
vitrinaPort = device.Key;
|
||||
Debug.Log($"нашел витрину на порту, {vitrinaPort}");
|
||||
}
|
||||
else if (device.Value == ComPortScanner.DeviceType.ControllerV1)
|
||||
else if (device.Value == ComPortScanner.DeviceType.Controller)
|
||||
{
|
||||
if (!controllerFound)
|
||||
{
|
||||
Debug.Log("Нашел периферийник1 открываю.");
|
||||
ComWrapper.Initialize(device.Key, boardVersion: 1);
|
||||
controllerFound = true;
|
||||
}
|
||||
}
|
||||
else if (device.Value == ComPortScanner.DeviceType.ControllerV2)
|
||||
{
|
||||
if (!controllerFound)
|
||||
{
|
||||
Debug.Log("Нашел периферийник2 открываю.");
|
||||
ComWrapper.Initialize(device.Key, boardVersion: 2);
|
||||
Debug.Log("Нашел периферийный контроллер, открываю.");
|
||||
ComWrapper.Initialize(device.Key);
|
||||
controllerFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ public class BackToMenuController : MonoBehaviour
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_canExit || In.GetAxis(JoystickSide.Both, InteractMode.Discrete).y <= 0)
|
||||
if (!_canExit || In.GetVerticalAxis(JoystickSide.Both, InteractMode.Discrete) <= 0)
|
||||
return;
|
||||
|
||||
Hide();
|
||||
|
||||
@ -26,6 +26,7 @@ public class Player : BasePlayer, IGamePlayer
|
||||
|
||||
private int SelectedColumn => availableColumns[selectedColumnIndex];
|
||||
|
||||
public JoystickSide ControlSide { get; set; } = JoystickSide.Left;
|
||||
public bool ControlsEnabled { get; set; } = true;
|
||||
|
||||
#region Public Methods
|
||||
@ -227,17 +228,14 @@ public class Player : BasePlayer, IGamePlayer
|
||||
|
||||
private int GetHorizontalInput()
|
||||
{
|
||||
return In.GetAxis(GetJoystickSide(), InteractMode.Discrete).x;
|
||||
return In.GetHorizontalAxis(ControlSide, InteractMode.Discrete);
|
||||
}
|
||||
|
||||
private bool IsActionPressed()
|
||||
{
|
||||
return In.GetAction(GetJoystickSide(), InteractMode.Discrete);
|
||||
return In.GetAction(ControlSide, InteractMode.Discrete);
|
||||
}
|
||||
|
||||
private JoystickSide GetJoystickSide()
|
||||
=> team == Team.Player1 ? JoystickSide.Left : JoystickSide.Right;
|
||||
|
||||
private void SetState(PlayerState newState)
|
||||
{
|
||||
state = newState;
|
||||
|
||||
@ -52,7 +52,13 @@ public class TurnManager : MonoBehaviour
|
||||
}
|
||||
|
||||
private void SetupPlayers()
|
||||
=> _currentPlayer = _player1;
|
||||
{
|
||||
_player1.ControlSide = _gameMode == GameMode.pve
|
||||
? JoystickSide.Both
|
||||
: JoystickSide.Left;
|
||||
_player2.ControlSide = JoystickSide.Right;
|
||||
_currentPlayer = _player1;
|
||||
}
|
||||
|
||||
private void StartTurn(IGamePlayer player)
|
||||
{
|
||||
|
||||
@ -277,8 +277,13 @@ public class COMinput : InputSource
|
||||
// Обновляет состояния инпута когда приходит новый ввод с платы. Дергается событием.
|
||||
private void UpdateInputState(string input)
|
||||
{
|
||||
input = input.Substring(0, PIN_COUNT);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
if (string.IsNullOrEmpty(input) || input.Length < PIN_COUNT)
|
||||
{
|
||||
Debug.LogWarning($"Получен короткий пакет COM-ввода: {input ?? "<null>"}");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < PIN_COUNT; i++)
|
||||
{
|
||||
// Получаем новое состояние пина
|
||||
bool newState = input[i] == POSITIVE;
|
||||
@ -306,7 +311,7 @@ public class COMinput : InputSource
|
||||
|
||||
char sensorPositive = ConfigManager.Config.SensorPositiveValue.ToString()[0];
|
||||
|
||||
if (prizeSensorPin < input.Length && input[prizeSensorPin] == sensorPositive)
|
||||
if (prizeSensorPin < PIN_COUNT && input[prizeSensorPin] == sensorPositive)
|
||||
{
|
||||
Debug.Log($"Считано положительное значени датчика приза ({sensorPositive}). Устанавливаю значение 1 в буфер");
|
||||
PlayerPrefs.SetInt("PrizeGivenBuffer", 1);
|
||||
|
||||
@ -30,19 +30,19 @@ public sealed class ComInputConfiguration
|
||||
public ComJoystickConfiguration LeftJoystick { get; set; } = new()
|
||||
{
|
||||
Up = 0,
|
||||
Right = 1,
|
||||
Down = 2,
|
||||
Left = 3,
|
||||
Action = 4
|
||||
Right = 2,
|
||||
Down = 3,
|
||||
Left = 1,
|
||||
Action = 8
|
||||
};
|
||||
|
||||
[JsonProperty("rightJoystick")]
|
||||
public ComJoystickConfiguration RightJoystick { get; set; } = new()
|
||||
{
|
||||
Up = 5,
|
||||
Right = 6,
|
||||
Down = 7,
|
||||
Left = 8,
|
||||
Up = 4,
|
||||
Right = 7,
|
||||
Down = 6,
|
||||
Left = 5,
|
||||
Action = 9
|
||||
};
|
||||
|
||||
|
||||
@ -2,10 +2,99 @@ using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
public enum ControllerAuthenticationState
|
||||
{
|
||||
NotStarted,
|
||||
AwaitingControllerVersion,
|
||||
ReadyToStartHandshake,
|
||||
AwaitingChallenge,
|
||||
AwaitingControllerProof,
|
||||
AwaitingReady,
|
||||
Ready,
|
||||
Failed
|
||||
}
|
||||
|
||||
public sealed class ControllerAuthenticator
|
||||
{
|
||||
private string _hostChallenge;
|
||||
|
||||
public ControllerAuthenticationState State { get; private set; }
|
||||
|
||||
public void BeginControllerReset()
|
||||
{
|
||||
_hostChallenge = null;
|
||||
State = ControllerAuthenticationState.AwaitingControllerVersion;
|
||||
}
|
||||
|
||||
public bool AcceptControllerVersion(string line)
|
||||
{
|
||||
if (State != ControllerAuthenticationState.AwaitingControllerVersion
|
||||
|| !BoardAuthentication.IsControllerVersionLine(line))
|
||||
return false;
|
||||
|
||||
State = ControllerAuthenticationState.ReadyToStartHandshake;
|
||||
return true;
|
||||
}
|
||||
|
||||
public string StartHandshake()
|
||||
{
|
||||
if (State != ControllerAuthenticationState.ReadyToStartHandshake)
|
||||
return null;
|
||||
|
||||
_hostChallenge = BoardAuthentication.CreateChallengeHex();
|
||||
State = ControllerAuthenticationState.AwaitingChallenge;
|
||||
return "07" + _hostChallenge;
|
||||
}
|
||||
|
||||
public string Handle(string line)
|
||||
{
|
||||
if (State == ControllerAuthenticationState.AwaitingChallenge
|
||||
&& BoardAuthentication.TryExtractHexPayload(line, "CHAL", out string controllerChallenge))
|
||||
{
|
||||
State = ControllerAuthenticationState.AwaitingControllerProof;
|
||||
return "08" + BoardAuthentication.EncryptHexBlock(controllerChallenge);
|
||||
}
|
||||
|
||||
if (State == ControllerAuthenticationState.AwaitingControllerProof
|
||||
&& BoardAuthentication.TryExtractHexPayload(line, "RAES", out string encryptedResponse))
|
||||
{
|
||||
if (!BoardAuthentication.ValidateEncryptedResponse(_hostChallenge, encryptedResponse))
|
||||
{
|
||||
Fail();
|
||||
return null;
|
||||
}
|
||||
|
||||
State = ControllerAuthenticationState.AwaitingReady;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (State == ControllerAuthenticationState.AwaitingReady
|
||||
&& string.Equals(line, "READY!", StringComparison.Ordinal))
|
||||
{
|
||||
State = ControllerAuthenticationState.Ready;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_hostChallenge = null;
|
||||
State = ControllerAuthenticationState.NotStarted;
|
||||
}
|
||||
|
||||
public void Fail()
|
||||
{
|
||||
_hostChallenge = null;
|
||||
State = ControllerAuthenticationState.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BoardAuthentication
|
||||
{
|
||||
private const int _blockSizeBytes = 16;
|
||||
private const int _hexBlockLength = _blockSizeBytes * 2;
|
||||
private const byte _keyObfuscationMask = 0x37;
|
||||
|
||||
private static readonly byte[] _aesObfuscatedKey =
|
||||
{
|
||||
@ -23,6 +112,10 @@ public static class BoardAuthentication
|
||||
return ToHex(challenge);
|
||||
}
|
||||
|
||||
public static bool IsControllerVersionLine(string line)
|
||||
=> !string.IsNullOrEmpty(line)
|
||||
&& line.IndexOf("ver_", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
|
||||
public static string EncryptHexBlock(string plainHex)
|
||||
{
|
||||
byte[] plain = ParseHexBlock(plainHex);
|
||||
@ -32,7 +125,7 @@ public static class BoardAuthentication
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.ECB;
|
||||
aes.Padding = PaddingMode.None;
|
||||
aes.Key = (byte[])_aesObfuscatedKey.Clone();
|
||||
aes.Key = GetAesKey();
|
||||
|
||||
using ICryptoTransform encryptor = aes.CreateEncryptor();
|
||||
byte[] encrypted = encryptor.TransformFinalBlock(plain, 0, plain.Length);
|
||||
@ -64,12 +157,9 @@ public static class BoardAuthentication
|
||||
if (!TryNormalizeHexBlock(responseHex, out string normalizedResponse))
|
||||
return false;
|
||||
|
||||
string expectedResponse = EncryptHexBlock(challengeHex);
|
||||
int difference = 0;
|
||||
for (int i = 0; i < expectedResponse.Length; i++)
|
||||
difference |= expectedResponse[i] ^ normalizedResponse[i];
|
||||
|
||||
return difference == 0;
|
||||
byte[] expectedResponse = ParseHexBlock(EncryptHexBlock(challengeHex));
|
||||
byte[] receivedResponse = ParseHexBlock(normalizedResponse);
|
||||
return CryptographicOperations.FixedTimeEquals(expectedResponse, receivedResponse);
|
||||
}
|
||||
|
||||
private static byte[] ParseHexBlock(string value)
|
||||
@ -105,6 +195,15 @@ public static class BoardAuthentication
|
||||
|| value >= 'A' && value <= 'F'
|
||||
|| value >= 'a' && value <= 'f';
|
||||
|
||||
private static byte[] GetAesKey()
|
||||
{
|
||||
byte[] key = new byte[_aesObfuscatedKey.Length];
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
key[i] = (byte)(_aesObfuscatedKey[i] ^ _keyObfuscationMask);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
private static string ToHex(byte[] bytes)
|
||||
{
|
||||
StringBuilder builder = new(bytes.Length * 2);
|
||||
|
||||
@ -6,30 +6,32 @@ using UnityEngine;
|
||||
public static class ComPortScanner
|
||||
{
|
||||
// Параметры соединения
|
||||
private const int BaudRate = 115200;
|
||||
private const int DataBits = 8;
|
||||
private const int Parity = 0; // 0 = None
|
||||
private const int StopBits = 1; // 1 = One
|
||||
private const int ReadTimeoutMs = 5;
|
||||
private const int WriteTimeoutMs = 10;
|
||||
private const int _baudRate = 115200;
|
||||
private const int _dataBits = 8;
|
||||
private const int _parity = 0; // 0 = None
|
||||
private const int _stopBits = 1; // 1 = One
|
||||
private const int _readTimeoutMs = 5;
|
||||
private const int _writeTimeoutMs = 10;
|
||||
|
||||
// Команды устройств
|
||||
private const string CmdPrize = "97"; // Для витрины и периферийника2
|
||||
private const string CmdControllerAuth = "07";
|
||||
private const int DeviceResponseTimeoutMs = 1000;
|
||||
private const string _cmdPrize = "97";
|
||||
private const string _cmdControllerReset = "00";
|
||||
private const string _cmdControllerAuth = "07";
|
||||
private const int _deviceResponseTimeoutMs = 1000;
|
||||
private const int _controllerRestartTimeoutMs = 20000;
|
||||
private const int _authenticationResponseTimeoutMs = 3000;
|
||||
|
||||
// Типы устройств
|
||||
public enum DeviceType
|
||||
{
|
||||
None,
|
||||
ControllerV1, // Периферийник1
|
||||
ControllerV2, // Периферийник2
|
||||
PrizeShowcase // Витрина
|
||||
Controller,
|
||||
PrizeShowcase
|
||||
}
|
||||
|
||||
// Префиксы ответов
|
||||
private const string RolePrefix = "ROLE :"; // Периферийник2 и витрина на 97
|
||||
private const string PrizeRoleValue = "vitrina"; // Витрина: ROLE : vitrina
|
||||
private const string _rolePrefix = "ROLE :";
|
||||
private const string _prizeRoleValue = "vitrina";
|
||||
|
||||
/// <summary>
|
||||
/// Сканирует все доступные COM-порты и возвращает словарь {порт = тип устройства}
|
||||
@ -62,32 +64,22 @@ public static class ComPortScanner
|
||||
Debug.Log($"Проверка порта {port}");
|
||||
string response97 = CheckDevice(
|
||||
port,
|
||||
CmdPrize,
|
||||
line => line.StartsWith(RolePrefix, StringComparison.Ordinal));
|
||||
if (response97 != null && response97.StartsWith(RolePrefix))
|
||||
_cmdPrize,
|
||||
line => line.StartsWith(_rolePrefix, StringComparison.Ordinal));
|
||||
if (response97 != null && response97.StartsWith(_rolePrefix))
|
||||
{
|
||||
Debug.Log($"Role: {response97}");
|
||||
// Извлекаем значение после "ROLE : "
|
||||
string roleValue = response97.Substring(RolePrefix.Length).Trim();
|
||||
string roleValue = response97.Substring(_rolePrefix.Length).Trim();
|
||||
|
||||
// Если роль = "vitrina" - это витрина
|
||||
if (string.Equals(roleValue, PrizeRoleValue, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(roleValue, _prizeRoleValue, StringComparison.OrdinalIgnoreCase))
|
||||
return DeviceType.PrizeShowcase;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DeviceType.ControllerV2;
|
||||
}
|
||||
|
||||
return DeviceType.Controller;
|
||||
}
|
||||
|
||||
string computerChallenge = BoardAuthentication.CreateChallengeHex();
|
||||
string authResponse = CheckDevice(
|
||||
port,
|
||||
CmdControllerAuth + computerChallenge,
|
||||
IsAuthenticationResponse);
|
||||
if (authResponse != null)
|
||||
return DeviceType.ControllerV1;
|
||||
if (CheckControllerDevice(port))
|
||||
return DeviceType.Controller;
|
||||
|
||||
return DeviceType.None;
|
||||
}
|
||||
@ -134,7 +126,7 @@ public static class ComPortScanner
|
||||
/// </summary>
|
||||
private static string CheckDevice(string port, string command, Func<string, bool> isExpectedResponse)
|
||||
{
|
||||
int handle = ComNativeWrapper.Serial_Open(port, BaudRate, DataBits, Parity, StopBits, 10, WriteTimeoutMs);
|
||||
int handle = ComNativeWrapper.Serial_Open(port, _baudRate, _dataBits, _parity, _stopBits, 10, _writeTimeoutMs);
|
||||
if (handle <= 0)
|
||||
{
|
||||
return null;
|
||||
@ -152,7 +144,7 @@ public static class ComPortScanner
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
long deadline = DateTime.UtcNow.Ticks
|
||||
+ DeviceResponseTimeoutMs * TimeSpan.TicksPerMillisecond;
|
||||
+ _deviceResponseTimeoutMs * TimeSpan.TicksPerMillisecond;
|
||||
while (DateTime.UtcNow.Ticks < deadline)
|
||||
{
|
||||
int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length);
|
||||
@ -172,7 +164,71 @@ public static class ComPortScanner
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAuthenticationResponse(string line)
|
||||
=> BoardAuthentication.TryExtractHexPayload(line, "CHAL", out _)
|
||||
|| BoardAuthentication.TryExtractHexPayload(line, "RAES", out _);
|
||||
private static bool CheckControllerDevice(string port)
|
||||
{
|
||||
int handle = ComNativeWrapper.Serial_Open(
|
||||
port,
|
||||
_baudRate,
|
||||
_dataBits,
|
||||
_parity,
|
||||
_stopBits,
|
||||
_readTimeoutMs,
|
||||
_writeTimeoutMs);
|
||||
if (handle <= 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
ClearPortBuffer(handle);
|
||||
|
||||
int resetWriteResult = ComNativeWrapper.Serial_WriteStringUTF8(
|
||||
handle,
|
||||
_cmdControllerReset,
|
||||
1);
|
||||
if (resetWriteResult < 0)
|
||||
return false;
|
||||
|
||||
string versionLine = ReadExpectedLine(
|
||||
handle,
|
||||
BoardAuthentication.IsControllerVersionLine,
|
||||
_controllerRestartTimeoutMs);
|
||||
if (versionLine == null)
|
||||
return false;
|
||||
|
||||
string computerChallenge = BoardAuthentication.CreateChallengeHex();
|
||||
int writeResult = ComNativeWrapper.Serial_WriteStringUTF8(
|
||||
handle,
|
||||
_cmdControllerAuth + computerChallenge,
|
||||
1);
|
||||
if (writeResult < 0)
|
||||
return false;
|
||||
|
||||
return ReadExpectedLine(handle, IsChallengeResponse, _authenticationResponseTimeoutMs) != null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ComNativeWrapper.Serial_Close(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ReadExpectedLine(int handle, Func<string, bool> isExpectedResponse, int timeoutMs)
|
||||
{
|
||||
byte[] buffer = new byte[1024];
|
||||
long deadline = DateTime.UtcNow.Ticks + timeoutMs * TimeSpan.TicksPerMillisecond;
|
||||
while (DateTime.UtcNow.Ticks < deadline)
|
||||
{
|
||||
int bytesRead = ComNativeWrapper.Serial_ReadLineUTF8_bytes(handle, buffer, buffer.Length);
|
||||
if (bytesRead <= 0)
|
||||
continue;
|
||||
|
||||
string line = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim();
|
||||
if (!string.IsNullOrEmpty(line) && isExpectedResponse(line))
|
||||
return line;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool IsChallengeResponse(string line)
|
||||
=> BoardAuthentication.TryExtractHexPayload(line, "CHAL", out _);
|
||||
}
|
||||
|
||||
@ -17,15 +17,12 @@ public static class ComWrapper
|
||||
public static event Action<string> InputRecieved;
|
||||
|
||||
private const string _requestInputCommand = "91";
|
||||
private const string _requestAuthCommand = "07";
|
||||
private const string _responseAuthCommand = "08";
|
||||
private const string _challengePrefix = "CHAL";
|
||||
private const string _encryptedResponsePrefix = "RAES";
|
||||
private const string _readyResponse = "READY!";
|
||||
private const string _controllerResetCommand = "00";
|
||||
private const string _cabinetLightingOnCommand = "856001";
|
||||
private const string _cabinetLightingOffCommand = "856000";
|
||||
private const int _authStepTimeoutMs = 3000;
|
||||
private const int _authReadyTimeoutMs = 5000;
|
||||
private const int _controllerRestartTimeoutMs = 20000;
|
||||
private const int _authHandshakeTimeoutMs = 10000;
|
||||
private const int _controllerInputBitCount = 26;
|
||||
private const int _inputIdleBeforePingMs = 60_000;
|
||||
private const int _keepAlivePollMs = 5000;
|
||||
private const int _reconnectRetryMs = 10_000;
|
||||
@ -38,14 +35,12 @@ public static class ComWrapper
|
||||
private static int _lastInputTick;
|
||||
private static string _controllerPort;
|
||||
private static volatile bool _isAuthorized;
|
||||
private static ILogger _logger;
|
||||
|
||||
public static int BoardVersion { get; private set; }
|
||||
public static bool IsPortOpen => h > 0;
|
||||
|
||||
private static ILogger logger;
|
||||
|
||||
public static void Initialize(string port, int boardVersion)//Добавить логгер в конструктор
|
||||
public static void Initialize(string port)
|
||||
{
|
||||
BoardVersion = boardVersion;
|
||||
_controllerPort = port;
|
||||
|
||||
if (_lifetimeCts != null) return;
|
||||
@ -70,7 +65,7 @@ public static class ComWrapper
|
||||
{
|
||||
int w = ComNativeWrapper.Serial_WriteStringUTF8(h, message, 1);
|
||||
}
|
||||
catch { logger?.Log($"Ошибка отправки строкового сигнала. {message}"); }
|
||||
catch { _logger?.Log($"Ошибка отправки строкового сигнала. {message}"); }
|
||||
}
|
||||
|
||||
private static void StartReaderThread()
|
||||
@ -143,7 +138,10 @@ public static class ComWrapper
|
||||
if (line == "COIN")
|
||||
{
|
||||
if (!_isAuthorized)
|
||||
{
|
||||
Send("10");
|
||||
return;
|
||||
}
|
||||
|
||||
PaymentReceived?.Invoke();
|
||||
Send("10");
|
||||
@ -157,7 +155,11 @@ public static class ComWrapper
|
||||
MarkInputActivity();
|
||||
|
||||
string rawInputString = line.Substring(5);
|
||||
string inputString = BoardVersion == 2 ? DecimalToBinary(rawInputString) : rawInputString;
|
||||
if (!TryConvertInput(rawInputString, out string inputString))
|
||||
{
|
||||
Debug.LogWarning($"Некорректный пакет ввода от контроллера: {line}");
|
||||
return;
|
||||
}
|
||||
|
||||
InputRecieved?.Invoke(inputString);
|
||||
}
|
||||
@ -196,45 +198,8 @@ public static class ComWrapper
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
/// <summary>
|
||||
/// Очищает буфер порта от накопившихся старых данных
|
||||
/// </summary>
|
||||
private static void FlushPortBuffer()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Проверяем валидность handle
|
||||
if (h <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Используем локальный буфер
|
||||
byte[] localBuf = new byte[1024];
|
||||
|
||||
int flushedCount = 0;
|
||||
|
||||
// Читаем все накопившиеся данные из порта
|
||||
int read;
|
||||
while ((read = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, localBuf, localBuf.Length)) > 0)
|
||||
{
|
||||
flushedCount++;
|
||||
if (flushedCount > 100) break; // Защита от бесконечного цикла
|
||||
}
|
||||
|
||||
Log($"Очищено: {flushedCount} сообщений");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log($"FlushPortBuffer exception: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Task<string> WaitForPrefixAsync(string prefix, int timeoutMs, CancellationToken ct)
|
||||
=> WaitForAsync(s => s.StartsWith(prefix, StringComparison.Ordinal), timeoutMs, ct);
|
||||
static Task<string> WaitForExactAsync(string expected, int timeoutMs, CancellationToken ct)
|
||||
=> WaitForAsync(s => string.Equals(s, expected, StringComparison.Ordinal), timeoutMs, ct);
|
||||
|
||||
static void OnLine(string line)
|
||||
{
|
||||
@ -296,67 +261,26 @@ public static class ComWrapper
|
||||
else
|
||||
{
|
||||
Log($"Открыл порт: {port}");
|
||||
FlushPortBuffer();
|
||||
|
||||
}
|
||||
|
||||
WaitAllCoins();
|
||||
}
|
||||
|
||||
static void WaitAllCoins()
|
||||
private static bool TryConvertInput(string decimalString, out string input)
|
||||
{
|
||||
int count = 0;
|
||||
int maxCount = 100;
|
||||
long timeout = 3000;
|
||||
input = null;
|
||||
if (!uint.TryParse(decimalString, out uint decimalValue))
|
||||
return false;
|
||||
|
||||
byte[] bytes = new byte[1024];
|
||||
long startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
long lastCoinTime = startTime;
|
||||
while (DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - lastCoinTime < 250)
|
||||
{
|
||||
int w = ComNativeWrapper.Serial_ReadLineUTF8_bytes(h, bytes, bytes.Length);
|
||||
if (w <= 0) continue;
|
||||
if( Encoding.UTF8.GetString(bytes, 0, w) == "COIN")
|
||||
{
|
||||
lastCoinTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
Send("10");
|
||||
count++;
|
||||
}
|
||||
if (count >= maxCount) break;
|
||||
if(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime >= timeout) break;
|
||||
}
|
||||
Debug.Log($"Before game coins: {count}");
|
||||
}
|
||||
uint maximumValue = (1u << _controllerInputBitCount) - 1u;
|
||||
if (decimalValue > maximumValue)
|
||||
return false;
|
||||
|
||||
/// <summary>
|
||||
/// Преобразует строку с десятичным числом в двоичное представление.
|
||||
/// Младшие биты слева, старшие справа (перевёрнутый порядок для соответствия индексам пинов).
|
||||
/// </summary>
|
||||
/// <param name="decimalString">Строка с десятичным числом</param>
|
||||
/// <returns>Строка с двоичным представлением</returns>
|
||||
private static string DecimalToBinary(string decimalString)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (long.TryParse(decimalString, out long decimalValue))
|
||||
{
|
||||
// Стандартное преобразование: старшие биты слева
|
||||
string binary = Convert.ToString(decimalValue, 2);
|
||||
|
||||
// Переворачиваем строку: младшие биты слева, старшие справа
|
||||
char[] chars = binary.ToCharArray();
|
||||
Array.Reverse(chars);
|
||||
return new string(chars);
|
||||
}
|
||||
|
||||
Debug.Log($"Не удалось распарсить значение INPUT: {decimalString}");
|
||||
return "11111111111111111111111111"; // 26 единиц по умолчанию
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.Log($"Ошибка преобразования в двоичную систему: {e.Message}");
|
||||
return "11111111111111111111111111"; // 26 единиц по умолчанию
|
||||
}
|
||||
string binary = Convert
|
||||
.ToString(decimalValue, 2)
|
||||
.PadLeft(_controllerInputBitCount, '0');
|
||||
char[] bits = binary.ToCharArray();
|
||||
Array.Reverse(bits);
|
||||
input = new string(bits);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -364,45 +288,48 @@ public static class ComWrapper
|
||||
{
|
||||
using CancellationTokenSource authCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
CancellationToken authToken = authCts.Token;
|
||||
string computerChallenge = BoardAuthentication.CreateChallengeHex();
|
||||
ControllerAuthenticator authenticator = new();
|
||||
authenticator.BeginControllerReset();
|
||||
|
||||
try
|
||||
{
|
||||
Task<string> controllerChallengeTask = WaitForAsync(
|
||||
line => BoardAuthentication.TryExtractHexPayload(line, _challengePrefix, out _),
|
||||
_authStepTimeoutMs,
|
||||
Task<string> versionTask = WaitForAsync(
|
||||
BoardAuthentication.IsControllerVersionLine,
|
||||
_controllerRestartTimeoutMs,
|
||||
authToken);
|
||||
Send(_requestAuthCommand + computerChallenge);
|
||||
Log("Перезагружаю контроллер перед авторизацией.");
|
||||
Send(_controllerResetCommand);
|
||||
string versionLine = await versionTask;
|
||||
Log($"Контроллер готов: {versionLine}");
|
||||
|
||||
string controllerChallengeLine = await controllerChallengeTask;
|
||||
if (!BoardAuthentication.TryExtractHexPayload(
|
||||
controllerChallengeLine,
|
||||
_challengePrefix,
|
||||
out string controllerChallenge))
|
||||
if (!authenticator.AcceptControllerVersion(versionLine))
|
||||
return false;
|
||||
|
||||
Task<string> encryptedResponseTask = WaitForAsync(
|
||||
line => BoardAuthentication.TryExtractHexPayload(line, _encryptedResponsePrefix, out _),
|
||||
_authStepTimeoutMs,
|
||||
authToken);
|
||||
Task<string> readyTask = WaitForExactAsync(
|
||||
_readyResponse,
|
||||
_authReadyTimeoutMs,
|
||||
authToken);
|
||||
Send(_responseAuthCommand + BoardAuthentication.EncryptHexBlock(controllerChallenge));
|
||||
string authenticationRequest = authenticator.StartHandshake();
|
||||
if (authenticationRequest == null)
|
||||
return false;
|
||||
|
||||
string encryptedResponseLine = await encryptedResponseTask;
|
||||
if (!BoardAuthentication.TryExtractHexPayload(
|
||||
encryptedResponseLine,
|
||||
_encryptedResponsePrefix,
|
||||
out string encryptedResponse)
|
||||
|| !BoardAuthentication.ValidateEncryptedResponse(computerChallenge, encryptedResponse))
|
||||
Task<string> authenticationTask = WaitForAsync(
|
||||
line =>
|
||||
{
|
||||
string response = authenticator.Handle(line);
|
||||
if (response != null)
|
||||
Send(response);
|
||||
|
||||
return authenticator.State == ControllerAuthenticationState.Ready
|
||||
|| authenticator.State == ControllerAuthenticationState.Failed;
|
||||
},
|
||||
_authHandshakeTimeoutMs,
|
||||
authToken);
|
||||
Send(authenticationRequest);
|
||||
await authenticationTask;
|
||||
|
||||
if (authenticator.State != ControllerAuthenticationState.Ready)
|
||||
{
|
||||
Log("Контроллер не прошёл взаимную AES-проверку.");
|
||||
return false;
|
||||
}
|
||||
|
||||
await readyTask;
|
||||
Log("Авторизация платы полностью завершена по READY!.");
|
||||
return true;
|
||||
}
|
||||
@ -412,6 +339,7 @@ public static class ComWrapper
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
authenticator.Fail();
|
||||
Log($"Ошибка авторизации платы: {exception.Message}");
|
||||
return false;
|
||||
}
|
||||
@ -576,5 +504,5 @@ public static class ComWrapper
|
||||
Send(_cabinetLightingOnCommand);
|
||||
}
|
||||
|
||||
private static void Log(string message) => logger?.Log($"CW{message}");
|
||||
private static void Log(string message) => _logger?.Log($"CW{message}");
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ public class In : MonoBehaviour
|
||||
public void Initialize()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
source = new KeyboardInput();
|
||||
source = ComWrapper.IsPortOpen ? new COMinput() : new KeyboardInput();
|
||||
#else
|
||||
source = new COMinput();
|
||||
#endif
|
||||
@ -21,35 +21,51 @@ public class In : MonoBehaviour
|
||||
|
||||
/// <summary>Возвращает горизонтальную и вертикальную оси выбранного джойстика.</summary>
|
||||
public static Vector2Int GetAxis(JoystickSide side, InteractMode interactMode = InteractMode.Discrete)
|
||||
=> new(
|
||||
GetHorizontalAxis(side, interactMode),
|
||||
GetVerticalAxis(side, interactMode));
|
||||
|
||||
public static int GetHorizontalAxis(
|
||||
JoystickSide side,
|
||||
InteractMode interactMode = InteractMode.Discrete)
|
||||
{
|
||||
if (side == JoystickSide.Both)
|
||||
{
|
||||
int horizontal = ToAxis(
|
||||
IsActive(InSignal.left1, interactMode) || IsActive(InSignal.left2, interactMode),
|
||||
IsActive(InSignal.right1, interactMode) || IsActive(InSignal.right2, interactMode));
|
||||
int vertical = ToAxis(
|
||||
IsActive(InSignal.down1, interactMode) || IsActive(InSignal.down2, interactMode),
|
||||
IsActive(InSignal.up1, interactMode) || IsActive(InSignal.up2, interactMode));
|
||||
|
||||
return new Vector2Int(horizontal, vertical);
|
||||
return ToAxis(
|
||||
IsEitherActive(InSignal.left1, InSignal.left2, interactMode),
|
||||
IsEitherActive(InSignal.right1, InSignal.right2, interactMode));
|
||||
}
|
||||
|
||||
bool isLeft = side == JoystickSide.Left;
|
||||
InSignal left = isLeft ? InSignal.left1 : InSignal.left2;
|
||||
InSignal right = isLeft ? InSignal.right1 : InSignal.right2;
|
||||
|
||||
return ToAxis(IsActive(left, interactMode), IsActive(right, interactMode));
|
||||
}
|
||||
|
||||
public static int GetVerticalAxis(
|
||||
JoystickSide side,
|
||||
InteractMode interactMode = InteractMode.Discrete)
|
||||
{
|
||||
if (side == JoystickSide.Both)
|
||||
{
|
||||
return ToAxis(
|
||||
IsEitherActive(InSignal.down1, InSignal.down2, interactMode),
|
||||
IsEitherActive(InSignal.up1, InSignal.up2, interactMode));
|
||||
}
|
||||
|
||||
bool isLeft = side == JoystickSide.Left;
|
||||
InSignal down = isLeft ? InSignal.down1 : InSignal.down2;
|
||||
InSignal up = isLeft ? InSignal.up1 : InSignal.up2;
|
||||
|
||||
return new Vector2Int(
|
||||
ToAxis(IsActive(left, interactMode), IsActive(right, interactMode)),
|
||||
ToAxis(IsActive(down, interactMode), IsActive(up, interactMode)));
|
||||
return ToAxis(IsActive(down, interactMode), IsActive(up, interactMode));
|
||||
}
|
||||
|
||||
/// <summary>Возвращает состояние кнопки Action выбранного джойстика.</summary>
|
||||
public static bool GetAction(JoystickSide side, InteractMode interactMode = InteractMode.Discrete)
|
||||
{
|
||||
if (side == JoystickSide.Both)
|
||||
return IsActive(InSignal.action1, interactMode) || IsActive(InSignal.action2, interactMode);
|
||||
return IsEitherActive(InSignal.action1, InSignal.action2, interactMode);
|
||||
|
||||
InSignal action = side == JoystickSide.Left ? InSignal.action1 : InSignal.action2;
|
||||
return IsActive(action, interactMode);
|
||||
@ -77,6 +93,16 @@ public class In : MonoBehaviour
|
||||
private static bool IsActive(InSignal signal, InteractMode interactMode)
|
||||
=> interactMode == InteractMode.Discrete ? GetButtonDown(signal) : GetButton(signal);
|
||||
|
||||
private static bool IsEitherActive(
|
||||
InSignal first,
|
||||
InSignal second,
|
||||
InteractMode interactMode)
|
||||
{
|
||||
bool firstActive = IsActive(first, interactMode);
|
||||
bool secondActive = IsActive(second, interactMode);
|
||||
return firstActive || secondActive;
|
||||
}
|
||||
|
||||
private static int ToAxis(bool negative, bool positive)
|
||||
=> (positive ? 1 : 0) - (negative ? 1 : 0);
|
||||
}
|
||||
@ -98,4 +124,4 @@ public enum InteractMode
|
||||
{
|
||||
Discrete,
|
||||
Continious
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ public class Menu : MonoBehaviour, IControlable
|
||||
|
||||
public void UpdateControls()
|
||||
{
|
||||
int direction_horizontal = In.GetAxis(JoystickSide.Both, InteractMode.Discrete).x;
|
||||
int direction_horizontal = In.GetHorizontalAxis(JoystickSide.Both, InteractMode.Discrete);
|
||||
menuPanelManager.SwitchPanel(direction_horizontal);
|
||||
|
||||
bool action = In.GetAction(JoystickSide.Both, InteractMode.Discrete);
|
||||
|
||||
@ -56,7 +56,7 @@ public class MenuPanelsScroll : MenuPanel
|
||||
{
|
||||
if (!m_enabled || isTransitioning || BlocksNavigation) return;
|
||||
|
||||
int dir_v = -In.GetAxis(JoystickSide.Both, InteractMode.Discrete).y;
|
||||
int dir_v = -In.GetVerticalAxis(JoystickSide.Both, InteractMode.Discrete);
|
||||
if (dir_v != 0)
|
||||
StartCoroutine(SwitchPanelCoroutine(dir_v));
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
{
|
||||
"leftJoystick": {
|
||||
"up": 0,
|
||||
"right": 1,
|
||||
"down": 2,
|
||||
"left": 3,
|
||||
"action": 4
|
||||
"right": 2,
|
||||
"down": 3,
|
||||
"left": 1,
|
||||
"action": 8
|
||||
},
|
||||
"rightJoystick": {
|
||||
"up": 5,
|
||||
"right": 6,
|
||||
"down": 7,
|
||||
"left": 8,
|
||||
"up": 4,
|
||||
"right": 7,
|
||||
"down": 6,
|
||||
"left": 5,
|
||||
"action": 9
|
||||
},
|
||||
"serviceButton": 10
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
"threadedArchiving": true,
|
||||
"logCacheMiss": false,
|
||||
"logAssetWarnings": true,
|
||||
"slimWriteResults": true,
|
||||
"slimWriteResults": false,
|
||||
"maximumCacheSize": 20,
|
||||
"useDetailedBuildLog": false,
|
||||
"useV2Hasher": true,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user