Files

479 lines
12 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.Collections;
using System.Collections.Generic;
using EasyChart;
using EasyChart.UGUI;
using UnityEngine;
using UnityEngine.UI;
public class settleChart : MonoBehaviour
{
[Header("Chart")]
[SerializeField] private ChartProfile sourceProfile;
[SerializeField] private UGUIChartBridge chartBridge;
[SerializeField] private RectTransform chartHost;
[Header("Paging")]
[SerializeField] private Button previousPageButton;
[SerializeField] private Button nextPageButton;
[SerializeField, Min(1)] private int notesPerPage = 100;
[Header("Build")]
[SerializeField, Min(1)] private int buildBatchSize = 64;
[SerializeField] private bool includeMissAsZero = true;
[SerializeField] private bool useHoldEndOffsetAsFallback = true;
private readonly List<float> allOffsets = new List<float>(1024);
private readonly List<string> allLabels = new List<string>(1024);
private ChartProfile runtimeProfile;
private Coroutine buildRoutine;
private int currentPageIndex;
private bool buttonsHooked;
private void Start()
{
HookButtons();
settlementController.OnSettlementCompleted += HandleSettlementCompleted;
}
private void OnEnable()
{
HookButtons();
}
private void OnDestroy()
{
settlementController.OnSettlementCompleted -= HandleSettlementCompleted;
UnhookButtons();
if (buildRoutine != null)
{
StopCoroutine(buildRoutine);
buildRoutine = null;
}
if (runtimeProfile != null)
{
Destroy(runtimeProfile);
runtimeProfile = null;
}
}
private void HandleSettlementCompleted()
{
if (buildRoutine != null)
{
StopCoroutine(buildRoutine);
}
buildRoutine = StartCoroutine(BuildChartAsync());
}
private IEnumerator BuildChartAsync()
{
ResolveReferences();
EnsureChartBridge();
if (chartBridge == null || sourceProfile == null)
{
yield break;
}
allOffsets.Clear();
allLabels.Clear();
currentPageIndex = 0;
UpdateButtonState();
yield return null;
BeatmapManager beatmapManager = BeatmapManager.Instance != null
? BeatmapManager.Instance
: SceneObjectLookupCache.FindAny<BeatmapManager>();
NoteData[] notes = beatmapManager != null && beatmapManager.beatmap != null
? beatmapManager.beatmap.notes
: null;
if (notes == null || notes.Length == 0)
{
ApplyPage(0);
buildRoutine = null;
yield break;
}
int visibleIndex = 0;
for (int i = 0; i < notes.Length; i++)
{
NoteData note = notes[i];
if (note == null)
{
continue;
}
if (!TryExtractSignedOffset(note, out float signedOffset))
{
continue;
}
visibleIndex++;
allOffsets.Add(signedOffset);
allLabels.Add(visibleIndex.ToString());
if ((i + 1) % buildBatchSize == 0)
{
yield return null;
}
}
ApplyPage(0);
buildRoutine = null;
}
private bool TryExtractSignedOffset(NoteData note, out float signedOffset)
{
if (!float.IsNaN(note.judgeOffsetMs))
{
signedOffset = note.judgeOffsetMs;
return true;
}
if (useHoldEndOffsetAsFallback && !float.IsNaN(note.judgeOffsetMsEnd))
{
signedOffset = note.judgeOffsetMsEnd;
return true;
}
bool isMiss = string.Equals(note.judgeResult, "Miss", StringComparison.OrdinalIgnoreCase)
|| string.Equals(note.judgeResultEnd, "Miss", StringComparison.OrdinalIgnoreCase);
if (includeMissAsZero && isMiss)
{
signedOffset = 0f;
return true;
}
signedOffset = 0f;
return false;
}
public void ShowPreviousPage()
{
if (currentPageIndex <= 0)
{
return;
}
ApplyPage(currentPageIndex - 1);
}
public void ShowNextPage()
{
int totalPages = GetTotalPages();
if (currentPageIndex >= totalPages - 1)
{
return;
}
ApplyPage(currentPageIndex + 1);
}
private void ApplyPage(int pageIndex)
{
EnsureChartBridge();
if (chartBridge == null || sourceProfile == null)
{
UpdateButtonState();
return;
}
EnsureRuntimeProfile();
if (runtimeProfile == null)
{
UpdateButtonState();
return;
}
int totalPages = Mathf.Max(1, GetTotalPages());
currentPageIndex = Mathf.Clamp(pageIndex, 0, totalPages - 1);
AxisConfig xAxis = FindAxis(runtimeProfile, runtimeProfile.xAxisId);
AxisConfig yAxis = FindAxis(runtimeProfile, runtimeProfile.yAxisId);
Serie targetSeries = FindPrimaryBarSeries(runtimeProfile);
if (xAxis == null || yAxis == null || targetSeries == null)
{
UpdateButtonState();
return;
}
int start = currentPageIndex * notesPerPage;
int remaining = Mathf.Max(0, allOffsets.Count - start);
int count = Mathf.Min(notesPerPage, remaining);
List<string> labels = new List<string>(Mathf.Max(1, count));
List<SeriesData> seriesData = new List<SeriesData>(Mathf.Max(1, count));
float maxAbs = 1f;
if (count <= 0)
{
labels.Add("-");
seriesData.Add(new SeriesData
{
id = Guid.NewGuid().ToString("N"),
x = 0f,
value = 0f,
name = "-"
});
}
else
{
for (int i = 0; i < count; i++)
{
int globalIndex = start + i;
float value = allOffsets[globalIndex];
maxAbs = Mathf.Max(maxAbs, Mathf.Abs(value));
string label = allLabels[globalIndex];
labels.Add(label);
seriesData.Add(new SeriesData
{
id = Guid.NewGuid().ToString("N"),
x = i,
value = value,
name = label
});
}
}
float roundedRange = Mathf.Max(1f, Mathf.Ceil(maxAbs / 5f) * 5f);
xAxis.axisType = AxisType.Category;
xAxis.labels = labels;
xAxis.showLabels = true;
xAxis.labelPlacement = CategoryLabelPlacement.Tick;
yAxis.axisType = AxisType.Value;
yAxis.autoRangeMin = false;
yAxis.autoRangeMax = false;
yAxis.autoTicks = false;
yAxis.minValue = -roundedRange;
yAxis.maxValue = roundedRange;
yAxis.splitCount = 4;
targetSeries.visible = true;
targetSeries.type = SerieType.Bar;
targetSeries.seriesData = seriesData;
targetSeries.EnsureIntegrity();
for (int i = 0; i < runtimeProfile.series.Count; i++)
{
if (!ReferenceEquals(runtimeProfile.series[i], targetSeries))
{
runtimeProfile.series[i].visible = false;
}
}
chartBridge.Profile = runtimeProfile;
chartBridge.Refresh();
UpdateButtonState();
}
private void EnsureRuntimeProfile()
{
if (runtimeProfile != null)
{
if (!ReferenceEquals(chartBridge.Profile, runtimeProfile))
{
chartBridge.Profile = runtimeProfile;
}
return;
}
runtimeProfile = Instantiate(sourceProfile);
runtimeProfile.name = sourceProfile.name + "_Runtime";
runtimeProfile.hideFlags = HideFlags.DontSave;
chartBridge.Profile = runtimeProfile;
}
private void EnsureChartBridge()
{
ResolveReferences();
if (chartBridge != null)
{
return;
}
RectTransform host = chartHost != null ? chartHost : transform as RectTransform;
if (host == null)
{
return;
}
chartBridge = host.GetComponent<UGUIChartBridge>();
if (chartBridge == null)
{
chartBridge = host.gameObject.AddComponent<UGUIChartBridge>();
}
if (chartBridge != null && sourceProfile != null)
{
chartBridge.Profile = runtimeProfile != null ? runtimeProfile : sourceProfile;
}
}
private void ResolveReferences()
{
if (chartHost == null)
{
Transform host = transform.Find("btm");
chartHost = host as RectTransform;
if (chartHost == null)
{
chartHost = GetComponentInChildren<RectTransform>(true);
}
}
if (chartBridge == null && chartHost != null)
{
chartBridge = chartHost.GetComponent<UGUIChartBridge>();
}
TryAutoBindButtons();
}
private void HookButtons()
{
if (buttonsHooked)
{
return;
}
ResolveReferences();
if (previousPageButton != null)
{
previousPageButton.onClick.AddListener(ShowPreviousPage);
}
if (nextPageButton != null)
{
nextPageButton.onClick.AddListener(ShowNextPage);
}
buttonsHooked = true;
UpdateButtonState();
}
private void UnhookButtons()
{
if (!buttonsHooked)
{
return;
}
if (previousPageButton != null)
{
previousPageButton.onClick.RemoveListener(ShowPreviousPage);
}
if (nextPageButton != null)
{
nextPageButton.onClick.RemoveListener(ShowNextPage);
}
buttonsHooked = false;
}
private void TryAutoBindButtons()
{
if (previousPageButton != null && nextPageButton != null)
{
return;
}
Button[] buttons = FindObjectsByType<Button>(FindObjectsInactive.Include, FindObjectsSortMode.None);
for (int i = 0; i < buttons.Length; i++)
{
Button button = buttons[i];
if (button == null)
{
continue;
}
string lowerName = button.name.ToLowerInvariant();
if (previousPageButton == null && (lowerName.Contains("prev") || lowerName.Contains("previous") || lowerName.Contains("lastpage") || lowerName.Contains("uppage") || lowerName.Contains("һ")))
{
previousPageButton = button;
continue;
}
if (nextPageButton == null && (lowerName.Contains("next") || lowerName.Contains("downpage") || lowerName.Contains("nextpage") || lowerName.Contains("һ")))
{
nextPageButton = button;
}
}
}
private void UpdateButtonState()
{
int totalPages = GetTotalPages();
if (previousPageButton != null)
{
previousPageButton.interactable = currentPageIndex > 0;
}
if (nextPageButton != null)
{
nextPageButton.interactable = currentPageIndex < totalPages - 1;
}
}
private int GetTotalPages()
{
if (allOffsets.Count <= 0)
{
return 1;
}
return Mathf.CeilToInt(allOffsets.Count / (float)Mathf.Max(1, notesPerPage));
}
private static AxisConfig FindAxis(ChartProfile profile, AxisId axisId)
{
if (profile == null || profile.axes == null)
{
return null;
}
for (int i = 0; i < profile.axes.Count; i++)
{
AxisConfig axis = profile.axes[i];
if (axis != null && axis.id == axisId)
{
return axis;
}
}
return null;
}
private static Serie FindPrimaryBarSeries(ChartProfile profile)
{
if (profile == null || profile.series == null || profile.series.Count == 0)
{
return null;
}
for (int i = 0; i < profile.series.Count; i++)
{
Serie serie = profile.series[i];
if (serie != null && serie.type == SerieType.Bar)
{
return serie;
}
}
return profile.series[0];
}
}