Danro-Jump/Assets/Scripts/Runtime/Settings/Core/ServiceSettingsStatisticsSummaryBuilder.cs
Danil Iarosh 839a68aef1 Improve VFX, UI overlays, session statistics, COM reliability, audio mixer, and expand architecture guides
Подробный список изменений:
- Перенесены эффекты частиц из C#-кода в готовые префабы-ассеты (Vfx Prefabs) в папке Assets/Resources/Vfx/ с автоматической генерацией материалов в Editor.
- Исправлены ошибки duration на играющих системах частиц и розовые текстуры в GameVfxService.cs.
- Исправлена навигация и фокус кнопок на оверлеях ContinueGameOverlay и PrizeResultOverlay с автоматическим подсвечиванием кнопок лимонно-зеленым цветом при фокусе и проигрыванием звука.
- Добавлена проверка настроек в GameplayHudController.cs для скрытия ContinueGameOverlay, если продолжение игры отключено.
- Расширен сбор статистики: создана структура GameplaySessionRecord, добавлена история до 50 раундов в PrizeSessionStateService и JsonPrizeSessionStateRepository, выведен расчет средних показателей и список 5 последних раундов в сервисной статистике.
- Повышена надежность COM-транспорта: добавлен sleep (80мс) в Dispose для graceful shutdown потоков, реализован цикл переподключения до 10 попыток при обрыве связи.
- Интегрированы настройки громкости с микшером: изменен шаг изменения громкости на 5% (0.05) для удобства, добавлен статус-чек и логи в ApplyServiceVolumes, расширены юнит-тесты на GetFloat параметры.
- Созданы новые архитектурные гайды по аппаратному COM-транспорту и призовой экономике автомата в папке GUIDES/architecture/.
- Оптимизирован и обновлен RAG-индекс проекта.
2026-07-03 15:04:08 +03:00

99 lines
5.8 KiB
C#
Raw Permalink 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.Linq;
using DanroJump.Prizes;
namespace DanroJump.Settings
{
/// <summary>
/// Собирает текст сервисной статистики без знания о маршрутизации кнопок.
/// </summary>
internal static class ServiceSettingsStatisticsSummaryBuilder
{
public static ServiceSettingsActionResult Build(
IReadOnlyServiceSettings settings,
IPrizeSessionStateService prizeSessionState)
{
var snapshot = settings?.Snapshot();
var totalSettings = snapshot?.Count ?? 0;
var visibleDefinitions = settings?.Database?.Entries.Count(entry => entry.ShownInServiceMenu) ?? 0;
var startCost = settings != null ? Math.Max(0, settings.GetInt(ServiceSettingsKeys.StartGameCost)) : 0;
var freePlay = settings != null && settings.GetBool(ServiceSettingsKeys.FreePlay);
var priceText = freePlay ? "free play" : $"{startCost}";
var prizeSummary = string.Empty;
if (prizeSessionState != null)
{
var historyList = prizeSessionState.History;
var recentHistory = historyList != null && historyList.Count > 0
? string.Join("\n", historyList.Skip(Math.Max(0, historyList.Count - 5)).Select(record =>
$"- [{record.StartedAtUtc:yyyy-MM-dd HH:mm:ss}] {FormatReward(record.Result.RewardKind)} ({record.Result.Score}очк) -> {FormatStatus(record.Status)}{(string.IsNullOrWhiteSpace(record.Note) ? "" : ": " + record.Note)}"))
: "История призов пуста.";
var gameplayHistory = prizeSessionState.GameplayHistory;
var avgScore = gameplayHistory != null && gameplayHistory.Count > 0
? gameplayHistory.Average(r => r.Score)
: 0f;
var avgCoins = gameplayHistory != null && gameplayHistory.Count > 0
? gameplayHistory.Average(r => r.Coins)
: 0f;
var avgHeight = gameplayHistory != null && gameplayHistory.Count > 0
? gameplayHistory.Average(r => r.MaxHeight)
: 0f;
var recentGameplay = gameplayHistory != null && gameplayHistory.Count > 0
? string.Join("\n", gameplayHistory.Skip(Math.Max(0, gameplayHistory.Count - 5)).Select(record =>
$"- [{record.Timestamp:yyyy-MM-dd HH:mm:ss}] очков: {record.Score}, монет: {record.Coins}, высота: {record.MaxHeight:F1}м, смертей: {record.DeathCount}, приз: {FormatReward(record.RewardKind)}"))
: "История игр пуста.";
prizeSummary = $"\n\n--- Эксплуатационная статистика ---\n" +
$"Всего сыграно игр: {prizeSessionState.TotalGamesPlayed}\n" +
$"Набрано очков всего: {prizeSessionState.TotalScore}\n" +
$"Собрано монет всего: {prizeSessionState.TotalCoins}\n" +
$"Средние показатели (последние сессии): очков {avgScore:F1}, монет {avgCoins:F1}, высота {avgHeight:F1}м\n" +
$"Показов QR-кодов: {prizeSessionState.QrShows}\n" +
$"Успешных физ. выдач: {prizeSessionState.PhysicalDispenses}\n" +
$"Сбоев физ. выдач: {prizeSessionState.PhysicalDispenseFailures}\n" +
$"Сбоев связи по COM: {prizeSessionState.ComErrors}\n" +
$"Призовые сессии: начато {prizeSessionState.StartedCount}, завершено {prizeSessionState.CompletedCount}, сбоев {prizeSessionState.FailedCount}, повторов заблокировано {prizeSessionState.DuplicateBlockedCount}\n" +
$"\n--- Последние Игры (до 5) ---\n{recentGameplay}\n" +
$"\n--- Последние Призовые сессии (до 5) ---\n{recentHistory}";
}
else
{
prizeSummary = "\nПризовая история недоступна в текущем контексте.";
}
return ServiceSettingsActionResult.Success(
"Статистика",
$"Настроек загружено: {totalSettings}. Видимых пунктов: {visibleDefinitions}. Стоимость старта: {priceText}.{prizeSummary}",
"Статистика");
}
private static string FormatReward(PrizeRewardKind rewardKind)
{
return rewardKind switch
{
PrizeRewardKind.QRCode => "QR",
PrizeRewardKind.Hopper => "Хоппер",
PrizeRewardKind.PrizeStand => "Витрина",
PrizeRewardKind.Big => "Большой приз",
PrizeRewardKind.Small => "Малый приз",
_ => "Нет приза",
};
}
private static string FormatStatus(PrizeSessionStatus status)
{
return status switch
{
PrizeSessionStatus.Pending => "ожидает показа",
PrizeSessionStatus.Shown => "показана",
PrizeSessionStatus.Completed => "завершена",
PrizeSessionStatus.Failed => "ошибка",
PrizeSessionStatus.DuplicateBlocked => "повтор заблокирован",
_ => status.ToString(),
};
}
}
}