479 lines
12 KiB
C#
479 lines
12 KiB
C#
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];
|
||
}
|
||
}
|