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 allOffsets = new List(1024); private readonly List allLabels = new List(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 : FindAnyObjectByType(); 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 labels = new List(Mathf.Max(1, count)); List seriesData = new List(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(); if (chartBridge == null) { chartBridge = host.gameObject.AddComponent(); } 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(true); } } if (chartBridge == null && chartHost != null) { chartBridge = chartHost.GetComponent(); } 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