Danil Iarosh 32cfa79578 refactor: Refactor work with COM to ComSystemManager
- Добавлен ComSystemManager как новый публичный компонент COM-системы для поэтапной замены ComWrapper
- Реализовано получение роли контроллера только после успешного handshake в ComDeviceClient
- Добавлено публичное поле ControllerRole в ComDeviceClient
- Добавлен запрос роли по команде 97 и разбор ответа формата ROLE : <value> (с пробелами)
- В ComMultiManager добавлена логика выбора DefaultDeviceId по роли main_controller
- Добавлен доступ к роли устройства через GetDeviceRole(deviceId) в менеджере и фасаде
- В ComWrapper добавлены настройки роли: roleRequestCommand, roleResponsePrefix, roleRequestTimeoutMs, mainControllerRole
- Дефолтная команда запроса роли изменена на 97
- Выполнена кодовая миграция зависимостей с ComWrapper на ComSystemManager в GameManager, GlobalsManager, PaymentManager, BalanceManager, InactivityManager, COMInput, LightController, PrizeSystem
- В GlobalsManager исправлена инициализация ссылки после AddComponent<ComSystemManager>(), чтобы исключить NullReference
2026-02-20 14:59:16 +03:00

182 lines
8.5 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.Collections;
using UnityEngine;
using Random = UnityEngine.Random;
namespace RR.Common.Controls
{
/// <summary>
/// Управляет подсветкой периферийной платы по протоколу <c>85XXYY</c>:
/// <list type="bullet">
/// <item><c>XX</c> — код эффекта (<see cref="LightMode"/>)</item>
/// <item><c>YY</c> — время в секундах (00–99)</item>
/// </list>
/// Команда <c>85</c> (без параметров) отключает рендеринг и освобождает память.
/// <para>Поддерживает цепочки эффектов через <see cref="LightSequence"/>:</para>
/// <code>
/// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop();
/// </code>
/// </summary>
public class LightController : MonoBehaviour
{
// ───────────────────────── Инспектор ─────────────────────────
[Header("Команды шляпы")]
[Tooltip("COM-команда для включения шляпы")]
[SerializeField] private string hatOnCommand = "2131";
[Tooltip("COM-команда для выключения шляпы")]
[SerializeField] private string hatOffCommand = "2130";
[Header("Мигание")]
[Tooltip("Интервал между переключениями при мигании (секунды)")]
[SerializeField] [Range(0.05f, 5f)] private float blinkInterval = 0.5f;
[Tooltip("Случайный разброс интервала (±секунды). 0 = строго равномерно")]
[SerializeField] [Range(0f, 2f)] private float blinkIntervalVariance;
[Header("Световые эффекты")]
[Tooltip("Эффект по умолчанию при вызове SetLight без аргументов")]
[SerializeField] private LightMode defaultLightMode = LightMode.Rainbow;
[Tooltip("Время эффекта по умолчанию (секунды, 0–99)")]
[SerializeField] [Range(0, 99)] private int defaultDuration = 3;
// ───────────────────────── Приватное состояние ─────────────────────────
private Coroutine _blinkingCoroutine;
private LightSequence _activeSequence;
private const string EffectPrefix = "85";
// ───────────────────────── Свойства ─────────────────────────
/// <summary>Время эффекта по умолчанию (используется в <see cref="LightSequence.Then(LightMode)"/>).</summary>
public int DefaultDuration => defaultDuration;
// ===========================start: PUBLIC API===========================
/// <summary>
/// Включает эффект по умолчанию и возвращает <see cref="LightSequence"/> для построения цепочки.
/// </summary>
public LightSequence SetLight() => SetLight(defaultLightMode, defaultDuration);
/// <summary>
/// Включает эффект на время по умолчанию и возвращает <see cref="LightSequence"/> для построения цепочки.
/// </summary>
public LightSequence SetLight(LightMode mode) => SetLight(mode, defaultDuration);
/// <summary>
/// Включает световой эффект на указанное время и возвращает <see cref="LightSequence"/> для построения цепочки.
/// <para>Если вызвать без <c>.Then()</c> / <c>.Loop()</c> — эффект запустится немедленно как одиночный.</para>
/// </summary>
/// <param name="mode">Эффект подсветки</param>
/// <param name="durationSeconds">Время эффекта (0–99 секунд, либо <c>-1</c> для команды без <c>YY</c>)</param>
public LightSequence SetLight(LightMode mode, int durationSeconds)
{
StopSequence();
_activeSequence = new LightSequence(this, mode, durationSeconds);
_activeSequence.Play();
return _activeSequence;
}
/// <summary>
/// Останавливает текущую активную последовательность эффектов и отключает рендеринг.
/// </summary>
public void StopSequence()
{
_activeSequence?.Stop();
_activeSequence = null;
}
/// <summary>
/// Останавливает все эффекты и отключает рендеринг платы (<c>85</c>).
/// </summary>
public void StopAllEffects()
{
StopSequence();
ComSystemManager.Send(EffectPrefix);
}
/// <summary>
/// Запускает мигание шляпы с настроенным интервалом.
/// </summary>
public void Blink() => Blink(blinkInterval);
/// <summary>
/// Запускает мигание шляпы с явно указанным интервалом.
/// </summary>
public void Blink(float interval)
{
StopBlink(enableHat: false);
_blinkingCoroutine = StartCoroutine(BlinkingCoroutine(interval));
}
/// <summary>
/// Меняет интервал мигания на лету; перезапускает мигание, если оно активно.
/// </summary>
public void SetBlinkInterval(float interval)
{
blinkInterval = Mathf.Max(0.05f, interval);
if (_blinkingCoroutine != null) Blink(blinkInterval);
}
/// <summary>
/// Останавливает мигание шляпы.
/// </summary>
/// <param name="enableHat">Оставить шляпу включённой после остановки</param>
public void StopBlink(bool enableHat = true)
{
if (_blinkingCoroutine != null)
{
StopCoroutine(_blinkingCoroutine);
_blinkingCoroutine = null;
}
ComSystemManager.Send(enableHat ? hatOnCommand : hatOffCommand);
}
/// <summary>Возвращает <c>true</c>, если мигание шляпы сейчас активно.</summary>
public bool IsBlinking => _blinkingCoroutine != null;
/// <summary>Возвращает <c>true</c>, если последовательность эффектов сейчас активна.</summary>
public bool IsSequencePlaying => _activeSequence?.IsPlaying ?? false;
// ===========================end: PUBLIC API===========================
/// <summary>
/// Отправляет COM-команду для одного шага последовательности.
/// Используется из <see cref="LightSequence"/>.
/// </summary>
public void SendCommand(LightMode mode, int durationSeconds)
{
ComSystemManager.Send(BuildCommand(mode, durationSeconds));
}
// ReSharper disable once IteratorNeverReturns — корутина намеренно бесконечная
private IEnumerator BlinkingCoroutine(float interval)
{
while (true)
{
ComSystemManager.Send(Random.Range(0f, 1f) < 0.5f ? hatOnCommand : hatOffCommand);
float wait = blinkIntervalVariance > 0f
? Mathf.Max(0.05f, interval + Random.Range(-blinkIntervalVariance, blinkIntervalVariance))
: interval;
yield return new WaitForSeconds(wait);
}
}
private static string BuildCommand(LightMode mode, int durationSeconds)
{
if (mode == LightMode.Off)
return EffectPrefix;
int xx = (int)mode;
if (durationSeconds == -1)
return $"{EffectPrefix}{xx:D2}";
int yy = Mathf.Clamp(durationSeconds, 0, 99);
return $"{EffectPrefix}{xx:D2}{yy:D2}";
}
}
}