diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c773680
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# Macintosh index files
+.DS_Store
diff --git a/core-dump/Scripts/FanSlideSegmentHandler.cs b/core-dump/Scripts/Contexts/Gameplay/Behaviors/Slide/Handlers/FanSlideSegmentHandler.cs
similarity index 100%
rename from core-dump/Scripts/FanSlideSegmentHandler.cs
rename to core-dump/Scripts/Contexts/Gameplay/Behaviors/Slide/Handlers/FanSlideSegmentHandler.cs
diff --git a/core-dump/Scripts/RegularSlideSegmentHandler.cs b/core-dump/Scripts/Contexts/Gameplay/Behaviors/Slide/Handlers/RegularSlideSegmentHandler.cs
similarity index 100%
rename from core-dump/Scripts/RegularSlideSegmentHandler.cs
rename to core-dump/Scripts/Contexts/Gameplay/Behaviors/Slide/Handlers/RegularSlideSegmentHandler.cs
diff --git a/core-dump/Scripts/SlideSegmentHandler.cs b/core-dump/Scripts/Contexts/Gameplay/Behaviors/Slide/Handlers/SlideSegmentHandler.cs
similarity index 100%
rename from core-dump/Scripts/SlideSegmentHandler.cs
rename to core-dump/Scripts/Contexts/Gameplay/Behaviors/Slide/Handlers/SlideSegmentHandler.cs
diff --git a/core-dump/Scripts/SlideBehaviour.cs b/core-dump/Scripts/Contexts/Gameplay/Behaviors/SlideBehaviour.cs
similarity index 100%
rename from core-dump/Scripts/SlideBehaviour.cs
rename to core-dump/Scripts/Contexts/Gameplay/Behaviors/SlideBehaviour.cs
diff --git a/core-dump/Scripts/CurveCcwGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/CurveCcwGenerator.cs
similarity index 100%
rename from core-dump/Scripts/CurveCcwGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/CurveCcwGenerator.cs
diff --git a/core-dump/Scripts/CurveCwGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/CurveCwGenerator.cs
similarity index 100%
rename from core-dump/Scripts/CurveCwGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/CurveCwGenerator.cs
diff --git a/core-dump/Scripts/EdgeCurveCcwGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/EdgeCurveCcwGenerator.cs
similarity index 100%
rename from core-dump/Scripts/EdgeCurveCcwGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/EdgeCurveCcwGenerator.cs
diff --git a/core-dump/Scripts/EdgeCurveCwGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/EdgeCurveCwGenerator.cs
similarity index 100%
rename from core-dump/Scripts/EdgeCurveCwGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/EdgeCurveCwGenerator.cs
diff --git a/core-dump/Scripts/EdgeFoldGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/EdgeFoldGenerator.cs
similarity index 100%
rename from core-dump/Scripts/EdgeFoldGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/EdgeFoldGenerator.cs
diff --git a/core-dump/Scripts/FoldGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/FoldGenerator.cs
similarity index 100%
rename from core-dump/Scripts/FoldGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/FoldGenerator.cs
diff --git a/core-dump/Scripts/RingCcwGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/RingCcwGenerator.cs
similarity index 100%
rename from core-dump/Scripts/RingCcwGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/RingCcwGenerator.cs
diff --git a/core-dump/Scripts/RingCwGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/RingCwGenerator.cs
similarity index 100%
rename from core-dump/Scripts/RingCwGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/RingCwGenerator.cs
diff --git a/core-dump/Scripts/SlideGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/SlideGenerator.cs
similarity index 100%
rename from core-dump/Scripts/SlideGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/SlideGenerator.cs
diff --git a/core-dump/Scripts/StraightGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/StraightGenerator.cs
similarity index 100%
rename from core-dump/Scripts/StraightGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/StraightGenerator.cs
diff --git a/core-dump/Scripts/ZigZagSGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/ZigZagSGenerator.cs
similarity index 100%
rename from core-dump/Scripts/ZigZagSGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/ZigZagSGenerator.cs
diff --git a/core-dump/Scripts/ZigZagZGenerator.cs b/core-dump/Scripts/Contexts/Gameplay/SlideGenerators/ZigZagZGenerator.cs
similarity index 100%
rename from core-dump/Scripts/ZigZagZGenerator.cs
rename to core-dump/Scripts/Contexts/Gameplay/SlideGenerators/ZigZagZGenerator.cs
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/Extended/HealthStats.cs b/core-dump/Scripts/Models/Scoring/Metrics/Extended/HealthStats.cs
new file mode 100644
index 0000000..fe5ff14
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/Extended/HealthStats.cs
@@ -0,0 +1,20 @@
+using SimaiSharp.Structures;
+
+namespace AstroDX.Models.Scoring.Metrics.Extended
+{
+ public class HealthStats : IReactiveStatistic
+ {
+ public HealthStats(int maxHealth)
+ {
+ MaxHealth = maxHealth;
+ }
+
+ public void Push(in NoteType type, in JudgeData data)
+ {
+ Loss += GameSettings.Settings.Profile.Mods.LifeDrain.GetHealthLoss(type, data);
+ }
+
+ public int MaxHealth { get; }
+ public int Loss { get; private set; }
+ }
+}
\ No newline at end of file
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/IReactiveStatistic.cs b/core-dump/Scripts/Models/Scoring/Metrics/IReactiveStatistic.cs
new file mode 100644
index 0000000..b3c125d
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/IReactiveStatistic.cs
@@ -0,0 +1,9 @@
+using SimaiSharp.Structures;
+
+namespace AstroDX.Models.Scoring.Metrics
+{
+ public interface IReactiveStatistic
+ {
+ void Push(in NoteType type, in JudgeData data);
+ }
+}
\ No newline at end of file
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/Internal/AchievementStats.cs b/core-dump/Scripts/Models/Scoring/Metrics/Internal/AchievementStats.cs
new file mode 100644
index 0000000..a606cb7
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/Internal/AchievementStats.cs
@@ -0,0 +1,110 @@
+using System;
+
+namespace AstroDX.Models.Scoring.Metrics.Internal
+{
+ public sealed class AchievementStats
+ {
+ private readonly Statistics _statistics;
+ private readonly double _baseScore;
+
+ public AchievementStats(Statistics statistics)
+ {
+ _statistics = statistics;
+
+ var totalPointCount = statistics.JudgementStats.MaxTapCount +
+ statistics.JudgementStats.MaxTouchCount +
+ statistics.JudgementStats.MaxHoldCount * 2 +
+ statistics.JudgementStats.MaxSlideCount * 3 +
+ statistics.JudgementStats.MaxBreakCount * 5;
+
+ _baseScore = totalPointCount > 0 ? 100.00 / totalPointCount : 0;
+ }
+
+ /// Used when displaying achievement as text to avoid double rounding
+ public double GetAchievement(bool precise = false)
+ {
+ var baseAchievement = GetBaseAchievement();
+ var extraAchievement = _statistics.JudgementStats.BreakRecord.MaxCount > 0
+ ? _statistics.JudgementStats.BreakRecord.Extras /
+ _statistics.JudgementStats.BreakRecord.MaxCount
+ : 0;
+
+ var totalAchievement = baseAchievement +
+ extraAchievement;
+
+ return precise ? totalAchievement : Math.Round(totalAchievement, 4);
+ }
+
+ private double GetBaseAchievement()
+ {
+ if (_baseScore == 0)
+ return 0;
+
+ var tapAchievement = _statistics.JudgementStats.TapRecord.Points
+ * _baseScore;
+
+ var touchAchievement = _statistics.JudgementStats.TouchRecord.Points
+ * _baseScore;
+
+ var holdAchievement = _statistics.JudgementStats.HoldRecord.Points
+ * _baseScore * 2;
+
+ var slideAchievement = _statistics.JudgementStats.SlideRecord.Points
+ * _baseScore * 3;
+
+ var breakAchievement = _statistics.JudgementStats.BreakRecord.Points
+ * _baseScore * 5;
+
+ return tapAchievement + touchAchievement + holdAchievement + slideAchievement + breakAchievement;
+ }
+
+ public double CalculateMaxAchievable(bool includeExtras)
+ {
+ var tapAchievement = _statistics.JudgementStats.TapRecord.PassedCount * _baseScore;
+ var touchAchievement = _statistics.JudgementStats.TouchRecord.PassedCount * _baseScore;
+ var holdAchievement = _statistics.JudgementStats.HoldRecord.PassedCount * _baseScore * 2;
+ var slideAchievement = _statistics.JudgementStats.SlideRecord.PassedCount * _baseScore * 3;
+ var breakAchievement = _statistics.JudgementStats.BreakRecord.PassedCount * _baseScore * 5;
+
+ return includeExtras && _statistics.JudgementStats.BreakRecord.MaxCount > 0
+ ? tapAchievement + touchAchievement + holdAchievement + slideAchievement + breakAchievement +
+ (double)_statistics.JudgementStats.BreakRecord.PassedCount /
+ _statistics.JudgementStats.BreakRecord.MaxCount
+ : tapAchievement + touchAchievement + holdAchievement + slideAchievement + breakAchievement;
+ }
+
+ public ClearType EvaluateClearType(bool whilePlaying = false)
+ {
+ var achievement = GetAchievement();
+
+ var cleared = achievement >= 80 && _statistics.JudgementStats.AllNotesPassed;
+ var missed = _statistics.JudgementStats.JudgedMissCount >= 1;
+ var anyGood = _statistics.JudgementStats.JudgedGoodCount >= 1;
+ var anyGreat = _statistics.JudgementStats.JudgedGreatCount >= 1;
+
+ var chartHasBreaks = _statistics.JudgementStats.MaxBreakCount > 0;
+
+ if (!whilePlaying && !cleared)
+ return ClearType.Failed;
+
+ if (missed)
+ return ClearType.Clear;
+
+ if (anyGood)
+ return ClearType.FullCombo;
+
+ if (anyGreat)
+ return ClearType.FullComboPlus;
+
+ if (!chartHasBreaks)
+ return ClearType.AllPerfect;
+
+ var maxBreak = _statistics.JudgementStats.BreakRecord.CriticalCount ==
+ _statistics.JudgementStats.BreakRecord.PassedCount;
+
+ return maxBreak
+ ? ClearType.AllPerfectPlus
+ : ClearType.AllPerfect;
+ }
+ }
+}
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/Internal/ComboStats.cs b/core-dump/Scripts/Models/Scoring/Metrics/Internal/ComboStats.cs
new file mode 100644
index 0000000..cf56edc
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/Internal/ComboStats.cs
@@ -0,0 +1,23 @@
+using System;
+using SimaiSharp.Structures;
+
+namespace AstroDX.Models.Scoring.Metrics.Internal
+{
+ public sealed class ComboStats : IReactiveStatistic
+ {
+ public long lastCombo { get; private set; }
+ public long maxCombo { get; private set; }
+
+ public void Push(in NoteType type, in JudgeData data = default)
+ {
+ if (data.grade == JudgeGrade.Miss)
+ {
+ lastCombo = 0;
+ return;
+ }
+
+ lastCombo++;
+ maxCombo = Math.Max(lastCombo, maxCombo);
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/Internal/DxScoreStats.cs b/core-dump/Scripts/Models/Scoring/Metrics/Internal/DxScoreStats.cs
new file mode 100644
index 0000000..dcf28c8
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/Internal/DxScoreStats.cs
@@ -0,0 +1,49 @@
+using System.Linq;
+
+namespace AstroDX.Models.Scoring.Metrics.Internal
+{
+ public sealed class DxScoreStats
+ {
+ private readonly Statistics _statistics;
+
+ private const int CriticalWeight = 3;
+
+ private static readonly int[] Weights =
+ {
+ CriticalWeight, 2, 1, 0, 0
+ };
+
+ public DxScoreStats(Statistics statistics) => _statistics = statistics;
+
+ public uint TotalDxScore => _statistics.JudgementStats.MaxNoteCount * CriticalWeight;
+
+ ///
+ /// This is used to generate max health or max dx score.
+ ///
+ public uint MaxAchievableDxScore => _statistics.JudgementStats.JudgedNoteCount * CriticalWeight;
+
+ ///
+ /// This is used to generate the current health or dx score.
+ ///
+ public uint DxScore =>
+ (uint)(_statistics.JudgementStats.TapRecord.Judged.Sum(c => Weights[(int)c.grade]) +
+ _statistics.JudgementStats.TouchRecord.Judged.Sum(c => Weights[(int)c.grade]) +
+ _statistics.JudgementStats.HoldRecord.Judged.Sum(c => Weights[(int)c.grade]) +
+ _statistics.JudgementStats.SlideRecord.Judged.Sum(c => Weights[(int)c.grade]) +
+ _statistics.JudgementStats.BreakRecord.Judged.Sum(c => Weights[(int)c.grade]));
+
+ public int GetGrade()
+ {
+ var dxScorePercentage = (float)DxScore / TotalDxScore;
+
+ // // add 1 for every passed threshold
+ for (var i = 0; i < DxGradeThresholds.Thresholds.Length; i++)
+ {
+ if (dxScorePercentage < DxGradeThresholds.Thresholds[i])
+ return i;
+ }
+
+ return DxGradeThresholds.Thresholds.Length;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/Internal/FluctuationStats.cs b/core-dump/Scripts/Models/Scoring/Metrics/Internal/FluctuationStats.cs
new file mode 100644
index 0000000..06c90a8
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/Internal/FluctuationStats.cs
@@ -0,0 +1,38 @@
+using AstroDX.Utilities.Extensions;
+using SimaiSharp.Structures;
+
+namespace AstroDX.Models.Scoring.Metrics.Internal
+{
+ public sealed class FluctuationStats : IReactiveStatistic
+ {
+ public long EarlyCount { get; private set; }
+ public long LateCount { get; private set; }
+ public double TotalDeviation { get; private set; }
+ public long NotesCounted { get; private set; }
+ public double Deviation => TotalDeviation / NotesCounted;
+
+ public void Push(in NoteType type, in JudgeData data)
+ {
+ if ((GameSettings.Settings.Profile.Metrics.fluctuationVisibility & data.grade.AsFlag()) == 0)
+ return;
+
+ if (type == NoteType.Slide)
+ return;
+
+ NotesCounted++;
+
+ // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
+ switch (data.state)
+ {
+ case JudgeState.Early:
+ EarlyCount++;
+ break;
+ case JudgeState.Late:
+ LateCount++;
+ break;
+ }
+
+ TotalDeviation += data.offset;
+ }
+ }
+}
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/Internal/JudgementStats.cs b/core-dump/Scripts/Models/Scoring/Metrics/Internal/JudgementStats.cs
new file mode 100644
index 0000000..b7c7bf6
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/Internal/JudgementStats.cs
@@ -0,0 +1,139 @@
+using System;
+using SimaiSharp.Structures;
+using UnityEngine;
+
+namespace AstroDX.Models.Scoring.Metrics.Internal
+{
+ public sealed class JudgementStats : IReactiveStatistic
+ {
+ public NoteRecord HoldRecord { get; }
+ public NoteRecord SlideRecord { get; }
+ public NoteRecord TapRecord { get; }
+ public NoteRecord TouchRecord { get; }
+ public NoteRecord BreakRecord { get; }
+
+ public uint MaxTapCount { get; }
+ public uint MaxTouchCount { get; }
+ public uint MaxHoldCount { get; }
+ public uint MaxSlideCount { get; }
+ public uint MaxBreakCount { get; }
+
+ public uint JudgedCriticalCount { get; private set; }
+ public uint JudgedPerfectCount { get; private set; }
+ public uint JudgedGreatCount { get; private set; }
+ public uint JudgedGoodCount { get; private set; }
+ public uint JudgedMissCount { get; private set; }
+ public uint JudgedNoteCount { get; private set; }
+
+ public uint MaxNoteCount =>
+ TapRecord.MaxCount + TouchRecord.MaxCount +
+ HoldRecord.MaxCount + SlideRecord.MaxCount +
+ BreakRecord.MaxCount;
+
+ public bool AllNotesPassed =>
+ TapRecord.MaxCount == TapRecord.PassedCount &&
+ TouchRecord.MaxCount == TouchRecord.PassedCount &&
+ HoldRecord.MaxCount == HoldRecord.PassedCount &&
+ SlideRecord.MaxCount == SlideRecord.PassedCount &&
+ BreakRecord.MaxCount == BreakRecord.PassedCount;
+
+ public JudgementStats(ReadOnlyMemory noteCollections)
+ {
+ foreach (var noteCollection in noteCollections.Span)
+ {
+ foreach (var note in noteCollection)
+ {
+ // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
+ switch (note.type)
+ {
+ case NoteType.Tap:
+ MaxTapCount++;
+ break;
+ case NoteType.Touch:
+ MaxTouchCount++;
+ break;
+ case NoteType.Hold:
+ MaxHoldCount++;
+ break;
+ case NoteType.Break:
+ MaxBreakCount++;
+ break;
+ }
+
+ foreach (var slide in note.slidePaths)
+ {
+ if (slide.type == NoteType.Break)
+ MaxBreakCount++;
+ else
+ MaxSlideCount++;
+ }
+ }
+ }
+
+ TapRecord = new NoteRecord(MaxTapCount);
+ TouchRecord = new NoteRecord(MaxTouchCount);
+ HoldRecord = new NoteRecord(MaxHoldCount);
+ SlideRecord = new NoteRecord(MaxSlideCount);
+ BreakRecord = new NoteRecord(MaxBreakCount);
+ }
+
+ public void Push(in NoteType type, in JudgeData data)
+ {
+ switch (type)
+ {
+ case NoteType.Tap:
+ TapRecord.Push(in data);
+ break;
+ case NoteType.Touch:
+ TouchRecord.Push(in data);
+ break;
+ case NoteType.Hold:
+ HoldRecord.Push(in data);
+ break;
+ case NoteType.Slide:
+ SlideRecord.Push(in data);
+ break;
+ case NoteType.Break:
+ BreakRecord.Push(in data);
+ BreakRecord.Extras += data.grade switch
+ {
+ JudgeGrade.CriticalPerfect => 1,
+ JudgeGrade.Perfect =>
+ Mathf.Abs((float)data.offset) <= 0.033335f ? 0.75 : 0.5,
+ JudgeGrade.Great => 0.4,
+ JudgeGrade.Good => 0.3,
+ JudgeGrade.Miss => 0,
+ _ => 0
+ };
+
+ break;
+ case NoteType.ForceInvalidate:
+ default:
+ break;
+ }
+
+ switch (data.grade)
+ {
+ case JudgeGrade.CriticalPerfect:
+ JudgedCriticalCount++;
+ break;
+ case JudgeGrade.Perfect:
+ JudgedPerfectCount++;
+ break;
+ case JudgeGrade.Great:
+ JudgedGreatCount++;
+ break;
+ case JudgeGrade.Good:
+ JudgedGoodCount++;
+ break;
+ case JudgeGrade.Miss:
+ JudgedMissCount++;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ JudgedNoteCount++;
+ }
+ }
+}
diff --git a/core-dump/Scripts/Models/Scoring/Metrics/Statistics.cs b/core-dump/Scripts/Models/Scoring/Metrics/Statistics.cs
new file mode 100644
index 0000000..5394946
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/Metrics/Statistics.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using AstroDX.Models.Scoring.Metrics.Internal;
+using Sigtrap.Relays;
+using SimaiSharp.Structures;
+
+namespace AstroDX.Models.Scoring.Metrics
+{
+ [Serializable]
+ public sealed class Statistics
+ {
+ public Relay OnJudgementReceived { get; } = new();
+
+ public FluctuationStats FluctuationStats { get; }
+ public ComboStats ComboStats { get; }
+ public JudgementStats JudgementStats { get; }
+ public AchievementStats AchievementStats { get; }
+ public DxScoreStats DxScoreStats { get; }
+ private HashSet Extensions { get; }
+
+ public Statistics(ReadOnlyMemory chart)
+ {
+ JudgementStats = new JudgementStats(chart);
+ FluctuationStats = new FluctuationStats();
+ ComboStats = new ComboStats();
+ AchievementStats = new AchievementStats(this);
+ DxScoreStats = new DxScoreStats(this);
+ Extensions = new HashSet();
+ }
+
+ public void Push(in NoteType type, in JudgeData data)
+ {
+ JudgementStats.Push(in type, in data);
+ ComboStats.Push(in type, in data);
+ FluctuationStats.Push(in type, in data);
+
+ foreach (var statistic in Extensions)
+ statistic.Push(in type, in data);
+
+ OnJudgementReceived.Dispatch(type, data);
+ }
+
+ public void RegisterExtension(IReactiveStatistic extension)
+ {
+ Extensions.Add(extension);
+ }
+
+ public IReactiveStatistic GetExtensionOrDefault()
+ where T : IReactiveStatistic
+ {
+ return Extensions.FirstOrDefault(e => e.GetType() == typeof(T));
+ }
+ }
+}
diff --git a/core-dump/Scripts/Models/Scoring/NoteRecord.cs b/core-dump/Scripts/Models/Scoring/NoteRecord.cs
new file mode 100644
index 0000000..b704c30
--- /dev/null
+++ b/core-dump/Scripts/Models/Scoring/NoteRecord.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+
+namespace AstroDX.Models.Scoring
+{
+ public class NoteRecord
+ {
+ public IReadOnlyList Judged { get; }
+
+ public uint MaxCount { get; }
+ public uint PassedCount { get; private set; }
+ public uint CriticalCount { get; private set; }
+ public uint PerfectCount { get; private set; }
+ public uint GreatCount { get; private set; }
+ public uint GoodCount { get; private set; }
+ public uint MissCount { get; private set; }
+ public double Extras { get; set; }
+
+ public NoteRecord(uint maxCount)
+ {
+ Judged = new List();
+ MaxCount = maxCount;
+ PassedCount = 0;
+ Extras = 0;
+ }
+
+ public void Push(in JudgeData data)
+ {
+ ((List)Judged).Add(data);
+
+ PassedCount++;
+ switch (data.grade)
+ {
+ case JudgeGrade.CriticalPerfect:
+ CriticalCount++;
+ break;
+ case JudgeGrade.Perfect:
+ PerfectCount++;
+ break;
+ case JudgeGrade.Great:
+ GreatCount++;
+ break;
+ case JudgeGrade.Good:
+ GoodCount++;
+ break;
+ case JudgeGrade.Miss:
+ MissCount++;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ Points += GetScore(data.grade);
+ }
+
+ public double Points { get; private set; }
+
+ private static double GetScore(JudgeGrade grade)
+ {
+ return grade switch
+ {
+ JudgeGrade.CriticalPerfect => 1,
+ JudgeGrade.Perfect => 1,
+ JudgeGrade.Great => 0.8,
+ JudgeGrade.Good => 0.5,
+ JudgeGrade.Miss => 0,
+ _ => throw new
+ ArgumentOutOfRangeException()
+ };
+ }
+ }
+}
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces.meta b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces.meta
new file mode 100755
index 0000000..b56ca47
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6a0d1a0a74b14c94af6f7d4d943075d8
+timeCreated: 1710933820
\ No newline at end of file
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs
new file mode 100755
index 0000000..90a34c2
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using DG.Tweening;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.Pool;
+using UnityEngine.UI;
+
+namespace AstroDX.UI.RecyclingScrollRect.Interfaces
+{
+ [RequireComponent(typeof(ScrollRect))]
+ public abstract class ScrollRectRecyclingBase : MonoBehaviour,
+ IDragHandler,
+ IBeginDragHandler,
+ IInitializePotentialDragHandler,
+ IEndDragHandler
+ {
+ protected IList DataSource { get; private set; } = new List();
+
+ protected ObjectPool> itemsPool;
+
+ [field: Disable, SerializeField]
+ protected List> ShownItemsUnordered { get; private set; } = new();
+
+ [BeginGroup("List View")]
+ [NotNull, SerializeField, Tooltip("Where the items will be created.")]
+ protected ScrollRect scrollRect;
+
+ [NotNull, SerializeField, Tooltip("The visual representation of each item.")]
+ [EndGroup]
+ protected ScrollViewItem itemPrefab;
+
+ private bool _initialized;
+
+ protected virtual void OnEnable()
+ {
+ if (_initialized)
+ return;
+
+ itemsPool = new ObjectPool>(() =>
+ {
+ var item = Instantiate(itemPrefab, scrollRect.content);
+ item.DisableItem();
+ return item;
+ },
+ item => item.EnableItem(),
+ item => item.DisableItem());
+ }
+
+ public void SetDataSource(IList dataSource)
+ {
+ DataSource = dataSource;
+ ReloadItems();
+ }
+
+ ///
+ /// Reloads the scroll rect to match up with the data source.
+ ///
+ protected abstract void ReloadItems();
+
+ ///
+ /// Sets the current visible range, load and discards items depending on the previous range.
+ /// Set the item data again to items that can be recycled. Triggered when data source changes.
+ ///
+ protected abstract void UpdateVisibleRange(bool resetActiveItems);
+
+ ///
+ /// Jumps to the item in the scroll rect.
+ ///
+ public void JumpToItem(TData item)
+ {
+ // We don't do any error handling here so that the error is thrown and developers using this can debug what's happening.
+ JumpToIndex(DataSource.IndexOf(item));
+ }
+
+ ///
+ /// Scrolls to the item in the scroll rect.
+ ///
+ public void ScrollToItem(TData item, float duration, Ease easing = default)
+ {
+ // We don't do any error handling here so that the error is thrown and developers using this can debug what's happening.
+ ScrollToIndex(DataSource.IndexOf(item), duration, easing);
+ }
+
+ ///
+ /// Jumps to the index provided by the data source in the scroll rect.
+ ///
+ public abstract void JumpToIndex(int index);
+
+ public abstract void ScrollToIndex(int index, float duration, Ease easing = default);
+
+ protected virtual void OnDestroy() => itemsPool.Dispose();
+
+ public abstract void OnDrag(PointerEventData eventData);
+
+ public abstract void OnBeginDrag(PointerEventData eventData);
+
+ public abstract void OnInitializePotentialDrag(PointerEventData eventData);
+
+ public abstract void OnEndDrag(PointerEventData eventData);
+ }
+}
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs.meta b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs.meta
new file mode 100755
index 0000000..3d1aa38
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5843eb5b52bc4cd085d670e17ff8be63
+timeCreated: 1710933385
\ No newline at end of file
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs
new file mode 100755
index 0000000..d254b30
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs
@@ -0,0 +1,34 @@
+using UnityEngine;
+
+namespace AstroDX.UI.RecyclingScrollRect.Interfaces
+{
+ public abstract class ScrollViewItem : MonoBehaviour
+ {
+ public T Data { get; private set; }
+ public int Index { get; private set; }
+
+ public void SetData(T data, int index)
+ {
+ Data = data;
+ Index = index;
+
+ OnDataUpdated();
+ }
+
+ ///
+ /// This is called whenever this instance is requested to render a new item with a different index.
+ ///
+ protected abstract void OnDataUpdated();
+
+ ///
+ /// When this object instance scrolls out of view, this is called to disable relevant components;
+ /// Should be as lightweight as possible since disabled items might get reused instantly.
+ ///
+ internal abstract void DisableItem();
+
+ ///
+ /// Called when an item is retrieved from the pool.
+ ///
+ internal abstract void EnableItem();
+ }
+}
\ No newline at end of file
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs.meta b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs.meta
new file mode 100755
index 0000000..44ecd8d
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 2f1b83f97d7847728b5f776276799111
+timeCreated: 1710933857
\ No newline at end of file
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects.meta b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects.meta
new file mode 100755
index 0000000..0b44820
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d313c27e4a974b3baea75f0d80917046
+timeCreated: 1710933392
\ No newline at end of file
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs
new file mode 100755
index 0000000..b1c6ec1
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs
@@ -0,0 +1,236 @@
+using System;
+using System.Collections.Generic;
+using AstroDX.GameSettings;
+using AstroDX.UI.RecyclingScrollRect.Interfaces;
+using AstroDX.Utilities.Extensions;
+using DG.Tweening;
+using UnityEngine;
+using UnityEngine.EventSystems;
+
+namespace AstroDX.UI.RecyclingScrollRect.ScrollRects
+{
+ [Serializable]
+ public class RecyclingGridView : ScrollRectRecyclingBase
+ {
+ [BeginGroup("Scrolling")]
+ [SerializeField]
+ private float gap;
+
+ [SerializeField]
+ private float marginTop;
+
+ [SerializeField]
+ private float marginBottom;
+
+ [Disable, SerializeField]
+ private Vector2 itemSize;
+
+ [Disable, SerializeField]
+ private int visibleStart;
+
+ [Disable, SerializeField]
+ private int columnCount;
+
+ [Disable, SerializeField]
+ private int rowCount;
+
+ [SerializeField, Disable]
+ private float usableWidth;
+
+ [Disable, SerializeField]
+ private int maxVisibleCount;
+
+ [Disable, SerializeField]
+ private float contentHeight;
+
+ private Tweener _positionTween;
+
+ private void OnValidate()
+ {
+ itemSize = itemPrefab?.transform.AsRectTransform().sizeDelta ?? Vector2.zero;
+
+ scrollRect.content.anchorMin = new Vector2(0, 1);
+ scrollRect.content.anchorMax = new Vector2(1, 1);
+ scrollRect.content.pivot = new Vector2(0.5f, 1);
+ scrollRect.content.localScale = Vector3.one;
+
+ var dt = new DrivenRectTransformTracker();
+ dt.Clear();
+
+ //Object to drive the transform
+ dt.Add(this, scrollRect.content, DrivenTransformProperties.All);
+ }
+
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+ Settings.Profile.Graphics.scaleFactorChanged.AddListener(ReloadItems);
+ }
+
+ private void Update()
+ {
+ if (scrollRect.velocity.y == 0)
+ return;
+
+ UpdateVisibleRange(false);
+ }
+
+ protected override void ReloadItems()
+ {
+ columnCount = Mathf.FloorToInt((scrollRect.viewport.rect.width + gap) / (itemSize.x + gap));
+ usableWidth = columnCount * itemSize.x + (columnCount - 1) * gap;
+
+ rowCount = Mathf.CeilToInt((float)DataSource.Count / columnCount);
+
+ maxVisibleCount = Mathf.CeilToInt((scrollRect.viewport.rect.size.y + gap) / (itemSize.y + gap)) * columnCount;
+
+ contentHeight = marginTop + rowCount * (itemSize.y + gap) + marginBottom;
+ scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, contentHeight);
+
+ UpdateVisibleRange(true);
+ }
+
+ private readonly HashSet _itemsToAdd = new();
+
+ private int _previousStartIndexInclusive;
+ private int _previousEndIndexExclusive;
+
+ protected override void UpdateVisibleRange(bool resetActiveItems)
+ {
+ visibleStart = scrollRect.content.anchoredPosition.y <= marginTop
+ ? 0
+ : columnCount *
+ Mathf.FloorToInt((scrollRect.content.anchoredPosition.y - marginTop) / (itemSize.y + gap));
+
+ var startIndexInclusive = visibleStart;
+ var endIndexExclusive = Mathf.Min(startIndexInclusive + maxVisibleCount, DataSource.Count);
+
+ // If the visible range hasn't changed, just update the positions of all items in range
+ if (startIndexInclusive == _previousStartIndexInclusive &&
+ endIndexExclusive == _previousEndIndexExclusive)
+ {
+ foreach (var item in ShownItemsUnordered)
+ {
+ item.GetRectTransform().anchoredPosition = AbsolutePositionAtIndex(item.Index);
+
+ if (resetActiveItems)
+ item.SetData(DataSource[item.Index], item.Index);
+ }
+
+ return;
+ }
+
+ _previousStartIndexInclusive = startIndexInclusive;
+ _previousEndIndexExclusive = endIndexExclusive;
+
+ // Populate the list of items that should be shown by the end of this procedure
+ for (var i = startIndexInclusive; i < endIndexExclusive; i++)
+ _itemsToAdd.Add(i);
+
+ // Find and skip items that are already shown
+ // Since the shown items are unordered (for example, we may request to show 1 after 2 and 3 are shown, resulting in [2, 3, 1]),
+ // We can't make any assumptions about items not appearing later in the list (e.g. [1, 2, 4, 5, 6, 3])
+ for (var i = 0; i < ShownItemsUnordered.Count; i++)
+ {
+ var item = ShownItemsUnordered[i];
+
+ if (_itemsToAdd.Contains(item.Index))
+ {
+ item.GetRectTransform().anchoredPosition = AbsolutePositionAtIndex(item.Index);
+
+ if (resetActiveItems)
+ item.SetData(DataSource[item.Index], item.Index);
+
+ _itemsToAdd.Remove(item.Index);
+ }
+ else
+ {
+ // We don't need to render this item in the new visible range, so we can safely release it
+ itemsPool.Release(item);
+
+ // Modifying a list while enumerating it isn't recommended
+ // But we're doing it here for performance
+ ShownItemsUnordered.RemoveAt(i);
+ i--;
+ }
+ }
+
+ // Lastly, generate the actual missing items
+ foreach (var index in _itemsToAdd)
+ {
+ var itemInstance = itemsPool.Get();
+ ShownItemsUnordered.Add(itemInstance);
+ itemInstance.SetData(DataSource[index], index);
+ itemInstance.GetRectTransform().anchoredPosition = AbsolutePositionAtIndex(index);
+ }
+
+ _itemsToAdd.Clear();
+ }
+
+ public override void JumpToIndex(int index)
+ {
+ scrollRect.velocity = new Vector3(0, 0);
+ scrollRect.verticalNormalizedPosition = GetScrollPosition(index);
+ UpdateVisibleRange(false);
+ }
+
+ public override void ScrollToIndex(int index, float duration, Ease easing = default)
+ {
+ scrollRect.velocity = new Vector3(0, 0);
+ _positionTween?.Kill();
+
+ _positionTween = DOTween.To(() => scrollRect.verticalNormalizedPosition,
+ p =>
+ {
+ scrollRect.verticalNormalizedPosition = p;
+ UpdateVisibleRange(false);
+ },
+ GetScrollPosition(index),
+ duration).SetEase(easing);
+ }
+
+ private float GetScrollPosition(int index)
+ {
+ // absolute position at index starts 0 at the top, and decreases when descending
+ var normalizedPosition = 1 + AbsolutePositionAtIndex(index).y / contentHeight;
+ normalizedPosition -= ((itemSize.y + marginBottom) * (1 - normalizedPosition) - marginTop * normalizedPosition) /
+ contentHeight;
+
+ return normalizedPosition;
+ }
+
+ public override void OnInitializePotentialDrag(PointerEventData eventData)
+ {
+ if (eventData.button != PointerEventData.InputButton.Left)
+ return;
+
+ _positionTween?.Kill();
+ }
+
+ public override void OnBeginDrag(PointerEventData eventData)
+ {
+ }
+
+ public override void OnEndDrag(PointerEventData eventData)
+ {
+ }
+
+ public override void OnDrag(PointerEventData eventData)
+ {
+ if (eventData.button != PointerEventData.InputButton.Left)
+ return;
+
+ UpdateVisibleRange(false);
+ }
+
+ private Vector2 AbsolutePositionAtIndex(int index)
+ {
+ var column = index % columnCount;
+ var fullContainerWidth = scrollRect.content.rect.size.x;
+ var row = Mathf.FloorToInt((float)index / columnCount);
+
+ return new Vector2(column * (itemSize.x + gap) + (fullContainerWidth - usableWidth) / 2,
+ -marginTop - row * (itemSize.y + gap));
+ }
+ }
+}
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs.meta b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs.meta
new file mode 100755
index 0000000..0ba9cb4
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 163b88140e634e719f1a2e8ae214523f
+timeCreated: 1710933419
\ No newline at end of file
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs
new file mode 100755
index 0000000..9a9192e
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs
@@ -0,0 +1,207 @@
+using System.Collections.Generic;
+using AstroDX.UI.RecyclingScrollRect.Interfaces;
+using AstroDX.Utilities.Extensions;
+using DG.Tweening;
+using UnityEngine;
+using UnityEngine.EventSystems;
+
+namespace AstroDX.UI.RecyclingScrollRect.ScrollRects
+{
+ public class RecyclingVerticalListView : ScrollRectRecyclingBase
+ {
+ [BeginGroup("Scrolling")]
+ [SerializeField]
+ private float gap;
+
+ [SerializeField]
+ private float marginTop;
+
+ [SerializeField]
+ [EndGroup]
+ private float marginBottom;
+
+ [Disable, SerializeField]
+ private float itemHeight;
+
+ [Disable, SerializeField]
+ private int maxVisibleCount;
+
+ [Disable, SerializeField]
+ private float contentHeight;
+
+ private void OnValidate()
+ {
+ itemHeight = itemPrefab?.GetRectTransform().sizeDelta.y ?? 0;
+
+ scrollRect.content.anchorMin = new Vector2(0, 1);
+ scrollRect.content.anchorMax = new Vector2(1, 1);
+ scrollRect.content.pivot = new Vector2(0.5f, 1);
+ scrollRect.content.localScale = Vector3.one;
+
+ var dt = new DrivenRectTransformTracker();
+ dt.Clear();
+
+ //Object to drive the transform
+ dt.Add(this, scrollRect.content, DrivenTransformProperties.All);
+ }
+
+ private void Update()
+ {
+ if (scrollRect.velocity.y == 0)
+ return;
+
+ UpdateVisibleRange(false);
+ }
+
+ protected override void ReloadItems()
+ {
+ maxVisibleCount = Mathf.CeilToInt((scrollRect.viewport.rect.size.y + gap) / (itemHeight + gap));
+
+ contentHeight = marginTop + (itemHeight + gap) + marginBottom;
+ scrollRect.content.sizeDelta = new Vector2(scrollRect.content.sizeDelta.x, contentHeight);
+
+ UpdateVisibleRange(true);
+ }
+
+ private readonly HashSet _itemsToAdd = new();
+
+ private int _previousStartIndexInclusive;
+ private int _previousEndIndexExclusive;
+
+ protected override void UpdateVisibleRange(bool resetActiveItems)
+ {
+ var startIndexInclusive = scrollRect.content.anchoredPosition.y <= marginTop
+ ? 0
+ : Mathf.FloorToInt((scrollRect.content.anchoredPosition.y - marginTop) /
+ (itemHeight + gap));
+ var endIndexExclusive = Mathf.Min(startIndexInclusive + maxVisibleCount, DataSource.Count);
+
+ // If the visible range hasn't changed, just update the positions of all items in range
+ if (startIndexInclusive == _previousStartIndexInclusive &&
+ endIndexExclusive == _previousEndIndexExclusive)
+ {
+ foreach (var item in ShownItemsUnordered)
+ {
+ var position = item.GetRectTransform().anchoredPosition;
+ position.y = AbsolutePositionAtIndex(item.Index);
+ item.GetRectTransform().anchoredPosition = position;
+
+ if (resetActiveItems)
+ item.SetData(DataSource[item.Index], item.Index);
+ }
+
+ return;
+ }
+
+ _previousStartIndexInclusive = startIndexInclusive;
+ _previousEndIndexExclusive = endIndexExclusive;
+
+ // Populate the list of items that should be shown by the end of this procedure
+ for (var i = startIndexInclusive; i < endIndexExclusive; i++)
+ _itemsToAdd.Add(i);
+
+ // Find and skip items that are already shown
+ // Since the shown items are unordered (for example, we may request to show 1 after 2 and 3 are shown, resulting in [2, 3, 1]),
+ // We can't make any assumptions about items not appearing later in the list (e.g. [1, 2, 4, 5, 6, 3])
+ for (var i = 0; i < ShownItemsUnordered.Count; i++)
+ {
+ var item = ShownItemsUnordered[i];
+
+ if (_itemsToAdd.Contains(item.Index))
+ {
+ var position = item.GetRectTransform().anchoredPosition;
+ position.y = AbsolutePositionAtIndex(item.Index);
+ item.GetRectTransform().anchoredPosition = position;
+
+ if (resetActiveItems)
+ item.SetData(DataSource[item.Index], item.Index);
+
+ _itemsToAdd.Remove(item.Index);
+ }
+ else
+ {
+ // We don't need to render this item in the new visible range, so we can safely release it
+ itemsPool.Release(item);
+
+ // Modifying a list while enumerating it isn't recommended
+ // But we're doing it here for performance
+ ShownItemsUnordered.RemoveAt(i);
+ i--;
+ }
+ }
+
+ // Lastly, generate the actual missing items
+ foreach (var index in _itemsToAdd)
+ {
+ var itemInstance = itemsPool.Get();
+ ShownItemsUnordered.Add(itemInstance);
+ itemInstance.SetData(DataSource[index], index);
+
+ var position = itemInstance.GetRectTransform().anchoredPosition;
+ position.y = AbsolutePositionAtIndex(index);
+ itemInstance.GetRectTransform().anchoredPosition = position;
+ }
+
+ _itemsToAdd.Clear();
+ }
+
+ public override void JumpToIndex(int index)
+ {
+ scrollRect.velocity = new Vector3(0, 0);
+ scrollRect.verticalNormalizedPosition = GetScrollPosition(index);
+ UpdateVisibleRange(false);
+ }
+
+ private Tweener _positionTween;
+
+ public override void ScrollToIndex(int index, float duration, Ease easing = default)
+ {
+ scrollRect.velocity = Vector2.zero;
+ _positionTween?.Kill();
+ _positionTween = DOTween.To(() => scrollRect.verticalNormalizedPosition, p =>
+ {
+ scrollRect.verticalNormalizedPosition = p;
+ UpdateVisibleRange(false);
+ }, GetScrollPosition(index), duration).SetEase(easing);
+ }
+
+ private float GetScrollPosition(int index)
+ {
+ // absolute position at index starts 0 at the top, and decreases when descending
+ var normalizedPosition = 1 + AbsolutePositionAtIndex(index) / contentHeight;
+ normalizedPosition -= ((itemHeight + marginBottom) * (1 - normalizedPosition) - marginTop * normalizedPosition) /
+ contentHeight;
+
+ return normalizedPosition;
+ }
+
+ public override void OnInitializePotentialDrag(PointerEventData eventData)
+ {
+ if (eventData.button != PointerEventData.InputButton.Left)
+ return;
+
+ _positionTween?.Kill();
+ }
+
+ public override void OnBeginDrag(PointerEventData eventData)
+ {
+ }
+
+ public override void OnEndDrag(PointerEventData eventData)
+ {
+ }
+
+ public override void OnDrag(PointerEventData eventData)
+ {
+ if (eventData.button != PointerEventData.InputButton.Left)
+ return;
+
+ UpdateVisibleRange(false);
+ }
+
+ private float AbsolutePositionAtIndex(int index)
+ {
+ return -index * (itemHeight + gap) - marginTop;
+ }
+ }
+}
diff --git a/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs.meta b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs.meta
new file mode 100755
index 0000000..0812753
--- /dev/null
+++ b/core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6421e225fef64262bce238235e6f6e0a
+timeCreated: 1711013891
\ No newline at end of file
diff --git a/core-dump/Scripts/GameTime.cs b/core-dump/Scripts/Utilities/Deprecated/Timing/GameTime.cs
similarity index 100%
rename from core-dump/Scripts/GameTime.cs
rename to core-dump/Scripts/Utilities/Deprecated/Timing/GameTime.cs
diff --git a/core-dump/Scripts/NativeLinearRegression.cs b/core-dump/Scripts/Utilities/Deprecated/Timing/NativeLinearRegression.cs
similarity index 100%
rename from core-dump/Scripts/NativeLinearRegression.cs
rename to core-dump/Scripts/Utilities/Deprecated/Timing/NativeLinearRegression.cs
diff --git a/core-dump/Scripts/TitleManager.cs b/core-dump/Scripts/Utilities/Deprecated/Title/TitleManager.cs
similarity index 100%
rename from core-dump/Scripts/TitleManager.cs
rename to core-dump/Scripts/Utilities/Deprecated/Title/TitleManager.cs
diff --git a/core-dump/Scripts/Trigonometry.cs b/core-dump/Scripts/Utilities/Trigonometry.cs
similarity index 100%
rename from core-dump/Scripts/Trigonometry.cs
rename to core-dump/Scripts/Utilities/Trigonometry.cs