using System;
using RR.Game2D.Core.Managers;
using UnityEngine;
namespace RR.Common.Controls
{
public class COMInput : InputSource
{
//Количество = цифровые + 2 датчика.
//На момент написания количество цифровых входов соответствует разрядности платы
//Настроить количество пинов под свою настройку платы
private const int PIN_COUNT = 26;
private const char POSITIVE = '0';
private const char NEGATIVE = '1';
private bool[] buttonState = new bool[PIN_COUNT];
private bool[] buttonDown = new bool[PIN_COUNT];
private bool[] buttonUp = new bool[PIN_COUNT];
//Свойство для определения текущего игрока
//Должно возвращать 1 или 2
//Используется для получения состояния кнопок с пометкой ...CurrentPlayer
private int currentPlayer => 1;//ПЕРЕОПРЕДЕЛИТЕ ОПРЕДЕЛЕНИЕ ТЕКУЩЕГО ИГРОКА ПОД СВОЮ СИСТЕМУ.
private GameManager gm;//я получаю кто сейчас ходит через этого менеджера. Если у вас другое, допишите.
public override bool Initialize()
{
try
{
gm = GameManager.Instance;
//ОБЯЗАТЕЛЬНЫЙ БЛОК
ComWrapper.OnInputReceived.AddListener(UpdateInputState);
}
catch (Exception e)
{
GameManager.Logger.Log("COMInput", $"Что то в COMInput {e.ToString()}");
return false;
}
return true;
}
#region PUBLIC_API
//ЭТА ГРУППА МЕТОДОВ ДЛЯ ПОЛУЧЕНИЯ СОСТОЯНИЯ ЛЮБОЙ КНОПКИ ПО ПИНУ (enum ButtonPinMapping)
///
/// Получает состояние конкретной кнопки по её физическому пину.
/// Возвращает true если кнопка в данный момент нажата.
///
/// Пин кнопки на периферийной плате
/// True если кнопка нажата в текущем кадре
public override bool GetButton(ButtonPinMapping button) => buttonState[(int)button];
///
/// Проверяет, была ли кнопка нажата в начале текущего кадра.
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
///
/// Пин кнопки на периферийной плате
/// True если кнопка была нажата в этом кадре
public override bool GetButtonDown(ButtonPinMapping button) => ConsumeButtonEvent(buttonDown, button);
///
/// Проверяет, была ли кнопка отпущена в начале текущего кадра.
/// Удерживает true до тех пор, пока кто то не проверит, затем сбрасывает.
///
/// Пин кнопки на периферийной плате
/// True если кнопка была отпущена в этом кадре
public override bool GetButtonUp(ButtonPinMapping button) => ConsumeButtonEvent(buttonUp, button);
///
/// Получает состояние кнопки по числовому индексу пина напрямую из буфера состояний.
/// Обходит перечисление ButtonPinMapping для прямого доступа к пинам по индексу.
///
/// Числовой индекс пина (0-15)
/// true если кнопка нажата в текущий момент
///
/// Используется когда необходим прямой доступ к пинам без приведения к перечислению.
/// Подходит для сценариев где количество пинов может превышать размер перечисления.
///
public override bool GetButton(int pinIndex) => buttonState[pinIndex];
///
/// Проверяет, была ли кнопка нажата в начале текущего кадра по числовому индексу пина.
/// Возвращает true только в первом кадре после нажатия и сбрасывает флаг события.
///
/// Числовой индекс пина (0-15)
/// true если кнопка была нажата в этом кадре
///
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
///
public override bool GetButtonDown(int pinIndex) => ConsumeButtonEvent(buttonDown, pinIndex);
///
/// Проверяет, была ли кнопка отпущена в начале текущего кадра по числовому индексу пина.
/// Возвращает true только в первом кадре после отпускания и сбрасывает флаг события.
///
/// Числовой индекс пина (0-15)
/// true если кнопка была отпущена в этом кадре
///
/// Метод "потребляет" событие - после чтения флаг сбрасывается.
/// Использует общий механизм ConsumeButtonEvent для обработки одноразовых событий.
///
public override bool GetButtonUp(int pinIndex) => ConsumeButtonEvent(buttonUp, pinIndex);
// ------------------------------------------------------------------------------------
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК АКТИВНОГО ИГРОКА (проверка по enum PlayerAction)
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
///
/// Получает состояние действия для текущего игрока.
/// Возвращает true если действие активно (кнопка нажата) у текущего игрока.
///
/// Действие игрока (up, down, left, right, action, extra)
/// True если действие активно у текущего игрока
public override bool GetButtonCurrentPlayer(PlayerAction button)
=> GetButton(GetButtonForCurrentPlayer(button));
///
/// Проверяет, было ли действие начато текущим игроком в этом кадре.
/// Возвращает true только в первом кадре после начала действия.
///
/// Действие игрока
/// True если действие было начато в этом кадре
public override bool GetButtonDownCurrentPlayer(PlayerAction button)
=> GetButtonDown(GetButtonForCurrentPlayer(button));
///
/// Проверяет, было ли действие завершено текущим игроком в этом кадре.
/// Возвращает true только в первом кадре после завершения действия.
///
/// Действие игрока
/// True если действие было завершено в этом кадре
public override bool GetButtonUpCurrentPlayer(PlayerAction button)
=> GetButtonUp(GetButtonForCurrentPlayer(button));
// ------------------------------------------------------------------------------------
//ЭТА ГРУППА МЕТОДОВ ПОЗВОЛЯЕТ УЗНАТЬ СОСТОЯНИЯ КНОПОК ЛЮБОГО ИГРОКА (проверка по enum PlayerAction)
//Служебные кнопки типа startOnePlayer, startTwoPlayers, secret, extra, hasPrize, prizeGiven здесь не проверяются
///
/// Получает состояние действия для любого из игроков.
/// Возвращает true если действие активно у первого ИЛИ второго игрока.
///
/// Действие игрока
/// True если действие активно у любого игрока
public override bool GetButtonAnyPlayer(PlayerAction button)
=> CheckBothPlayers(button, GetButton);
///
/// Проверяет, было ли действие начато любым игроком в этом кадре.
/// Возвращает true если первый ИЛИ второй игрок начали действие в этом кадре.
///
/// Действие игрока
/// True если действие было начато любым игроком в этом кадре
public override bool GetButtonDownAnyPlayer(PlayerAction button)
=> CheckBothPlayers(button, GetButtonDown);
///
/// Проверяет, было ли действие завершено любым игроком в этом кадре.
/// Возвращает true если первый ИЛИ второй игрок завершили действие в этом кадре.
///
/// Действие игрока
/// True если действие было завершено любым игроком в этом кадре
public override bool GetButtonUpAnyPlayer(PlayerAction button)
=> CheckBothPlayers(button, GetButtonUp);
#endregion
//==========ХЕЛПЕРЫ==========
// Проверка нажатия с обоих контроллеров игроков
private bool CheckBothPlayers(PlayerAction action, Func buttonFunc)
{
var buttons = _buttonByAction(action);
return buttonFunc(buttons.player1button) || buttonFunc(buttons.player2button);
}
// Проверка нажатия с контроллера текущего игрока
private ButtonPinMapping GetButtonForCurrentPlayer(PlayerAction button)
{
var buttons = _buttonByAction(button);
return currentPlayer == 1 ? buttons.player1button : buttons.player2button;
}
// Маппер из действий игрока => пины платы
private (ButtonPinMapping player1button, ButtonPinMapping player2button) _buttonByAction(PlayerAction playerAction) =>
playerAction switch
{
PlayerAction.up => (ButtonPinMapping.up1, ButtonPinMapping.up2),
PlayerAction.down => (ButtonPinMapping.down1, ButtonPinMapping.down2),
PlayerAction.left => (ButtonPinMapping.left1, ButtonPinMapping.left2),
PlayerAction.right => (ButtonPinMapping.right1, ButtonPinMapping.right2),
PlayerAction.action => (ButtonPinMapping.action1, ButtonPinMapping.action2),
PlayerAction.extra => (ButtonPinMapping.extra, ButtonPinMapping.extra), // одинаковая кнопка
_ => throw new ArgumentException($"Unknown player action: {playerAction}")
};
// Обновляет состояния инпута когда приходит новый ввод с платы. Дергается событием.
private void UpdateInputState(string input)
{
// Будем принимать в нативном формате by Pasha. 0 - нажато 1 - не нажато (чтобы преобразовывать циклом)
// В параметрприходит строка вида "1111011111011111"
// Ограничиваем количество обрабатываемых символов размером массива
int maxIndex = Math.Min(input.Length, PIN_COUNT);
for (int i = 0; i < maxIndex; i++)
{
// Получаем новое состояние пина
bool newState = input[i] == POSITIVE;
// Состояние пина изменилось => пишем в буферы
if (newState != buttonState[i])
{
if (newState) // Пин был зажат
{
buttonDown[i] = true;
buttonUp[i] = false;
}
else // Пин был отжат
{
buttonDown[i] = false;
buttonUp[i] = true;
}
}
buttonState[i] = newState;
}
// Проверяем последний пин только если он в пределах массива
if (input.Length > 0 && input.Length <= PIN_COUNT && input[^1] == POSITIVE)
PlayerPrefs.SetInt("PrizeGivenBuffer", 1); //пишем в буфер чтобы между кадрами и порядком вызова не проебать
// DebugWindow.Message("Инпуты: " + string.Join("", buttonState));//можно вырезать
}
// Потребление "Нажатия"/"Отжатия". Возвращает что там было и чистит флаг из буфера
private static bool ConsumeButtonEvent(bool[] buffer, ButtonPinMapping button)
{
bool result = buffer[(int)button];
buffer[(int)button] = false;
return result;
}
///
/// Вспомогательный метод для обработки одноразовых событий кнопок по числовому индексу.
/// Возвращает текущее значение флага и сбрасывает его для предотвращения повторной обработки.
///
/// Буфер событий (buttonDown или buttonUp)
/// Числовой индекс пина
/// Состояние флага события до сброса
private static bool ConsumeButtonEvent(bool[] buffer, int pinIndex)
{
bool result = buffer[pinIndex];
buffer[pinIndex] = false;
return result;
}
}
}