Унифицировать работу с контроллером и улучшить загрузку персонажей

This commit is contained in:
Сергей Бондарь 2026-09-15 15:34:48 +03:00
parent ca42cf53ba
commit 70c4dbd081
15 changed files with 384 additions and 234 deletions

View File

@ -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))

View File

@ -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;
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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)
{

View File

@ -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);

View File

@ -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
};

View File

@ -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);

View File

@ -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 _);
}

View File

@ -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}");
}

View File

@ -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
}
}

View File

@ -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);

View File

@ -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));
}

View File

@ -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

View File

@ -5,7 +5,7 @@
"threadedArchiving": true,
"logCacheMiss": false,
"logAssetWarnings": true,
"slimWriteResults": true,
"slimWriteResults": false,
"maximumCacheSize": 20,
"useDetailedBuildLog": false,
"useV2Hasher": true,