From b776940c4881fe34fd5455c55e83722dd66895d5 Mon Sep 17 00:00:00 2001 From: Soneoy_Mac Date: Tue, 16 Apr 2024 03:40:27 -0700 Subject: [PATCH] Update scripts --- .gitignore | 2 + .../Slide/Handlers}/FanSlideSegmentHandler.cs | 0 .../Handlers}/RegularSlideSegmentHandler.cs | 0 .../Slide/Handlers}/SlideSegmentHandler.cs | 0 .../Gameplay/Behaviors}/SlideBehaviour.cs | 0 .../SlideGenerators}/CurveCcwGenerator.cs | 0 .../SlideGenerators}/CurveCwGenerator.cs | 0 .../SlideGenerators}/EdgeCurveCcwGenerator.cs | 0 .../SlideGenerators}/EdgeCurveCwGenerator.cs | 0 .../SlideGenerators}/EdgeFoldGenerator.cs | 0 .../SlideGenerators}/FoldGenerator.cs | 0 .../SlideGenerators}/RingCcwGenerator.cs | 0 .../SlideGenerators}/RingCwGenerator.cs | 0 .../SlideGenerators}/SlideGenerator.cs | 0 .../SlideGenerators}/StraightGenerator.cs | 0 .../SlideGenerators}/ZigZagSGenerator.cs | 0 .../SlideGenerators}/ZigZagZGenerator.cs | 0 .../Scoring/Metrics/Extended/HealthStats.cs | 20 ++ .../Scoring/Metrics/IReactiveStatistic.cs | 9 + .../Metrics/Internal/AchievementStats.cs | 110 ++++++++ .../Scoring/Metrics/Internal/ComboStats.cs | 23 ++ .../Scoring/Metrics/Internal/DxScoreStats.cs | 49 ++++ .../Metrics/Internal/FluctuationStats.cs | 38 +++ .../Metrics/Internal/JudgementStats.cs | 139 +++++++++++ .../Models/Scoring/Metrics/Statistics.cs | 55 ++++ .../Scripts/Models/Scoring/NoteRecord.cs | 72 ++++++ .../UI/RecyclingScrollRect/Interfaces.meta | 3 + .../Interfaces/ScrollRectRecyclingBase.cs | 101 ++++++++ .../ScrollRectRecyclingBase.cs.meta | 3 + .../Interfaces/ScrollViewItem.cs | 34 +++ .../Interfaces/ScrollViewItem.cs.meta | 3 + .../UI/RecyclingScrollRect/ScrollRects.meta | 3 + .../ScrollRects/RecyclingGridView.cs | 236 ++++++++++++++++++ .../ScrollRects/RecyclingGridView.cs.meta | 3 + .../ScrollRects/RecyclingVerticalListView.cs | 207 +++++++++++++++ .../RecyclingVerticalListView.cs.meta | 3 + .../Deprecated/Timing}/GameTime.cs | 0 .../Timing}/NativeLinearRegression.cs | 0 .../Deprecated/Title}/TitleManager.cs | 0 .../Scripts/{ => Utilities}/Trigonometry.cs | 0 40 files changed, 1113 insertions(+) create mode 100644 .gitignore rename core-dump/Scripts/{ => Contexts/Gameplay/Behaviors/Slide/Handlers}/FanSlideSegmentHandler.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/Behaviors/Slide/Handlers}/RegularSlideSegmentHandler.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/Behaviors/Slide/Handlers}/SlideSegmentHandler.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/Behaviors}/SlideBehaviour.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/CurveCcwGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/CurveCwGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/EdgeCurveCcwGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/EdgeCurveCwGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/EdgeFoldGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/FoldGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/RingCcwGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/RingCwGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/SlideGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/StraightGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/ZigZagSGenerator.cs (100%) rename core-dump/Scripts/{ => Contexts/Gameplay/SlideGenerators}/ZigZagZGenerator.cs (100%) create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/Extended/HealthStats.cs create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/IReactiveStatistic.cs create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/Internal/AchievementStats.cs create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/Internal/ComboStats.cs create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/Internal/DxScoreStats.cs create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/Internal/FluctuationStats.cs create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/Internal/JudgementStats.cs create mode 100644 core-dump/Scripts/Models/Scoring/Metrics/Statistics.cs create mode 100644 core-dump/Scripts/Models/Scoring/NoteRecord.cs create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/Interfaces.meta create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollRectRecyclingBase.cs.meta create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/Interfaces/ScrollViewItem.cs.meta create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects.meta create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingGridView.cs.meta create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs create mode 100755 core-dump/Scripts/UI/RecyclingScrollRect/ScrollRects/RecyclingVerticalListView.cs.meta rename core-dump/Scripts/{ => Utilities/Deprecated/Timing}/GameTime.cs (100%) rename core-dump/Scripts/{ => Utilities/Deprecated/Timing}/NativeLinearRegression.cs (100%) rename core-dump/Scripts/{ => Utilities/Deprecated/Title}/TitleManager.cs (100%) rename core-dump/Scripts/{ => Utilities}/Trigonometry.cs (100%) 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