From 0cecb2bba348e9d178704d911339ba6df7a1b26b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 19:33:19 +0900 Subject: [PATCH 001/164] Remove incorrect assumption from tests --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index f7909071ea..9e78185272 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Timing; @@ -194,13 +193,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(0); - AddStep("adjust track rate", () => MusicController.CurrentTrack.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate))); - // autoplay replay frames use track time; - // if a spin takes 1000ms in track time and we're playing with a 2x rate adjustment, the spin will take 500ms of *real* time. - // therefore we need to apply the rate adjustment to the replay itself to change from track time to real time, - // as real time is what we care about for spinners - // (so we're making the spin take 1000ms in real time *always*, regardless of the track clock's rate). - transformReplay(replay => applyRateAdjustment(replay, rate)); + AddStep("adjust track rate", () => Player.GameplayClockContainer.UserPlaybackRate.Value = rate); addSeekStep(1000); AddAssert("progress almost same", () => Precision.AlmostEquals(expectedProgress, drawableSpinner.Progress, 0.05)); From 3f788da06d0aa4379f8133ce319dcde18cabe1fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 19:39:54 +0900 Subject: [PATCH 002/164] Fix SPM changing incorrectly with playback rate changes --- .../Pieces/SpinnerRotationTracker.cs | 7 +++- .../Rulesets/UI/FrameStabilityContainer.cs | 8 +++- osu.Game/Screens/Play/GameplayClock.cs | 24 +++++++++++ .../Screens/Play/GameplayClockContainer.cs | 42 +++++++++++++++++-- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index f1a782cbb5..e949017ccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -77,6 +79,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private bool rotationTransferred; + [Resolved] + private GameplayClock gameplayClock { get; set; } + protected override void Update() { base.Update(); @@ -126,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - RateAdjustedRotation += (float)(Math.Abs(angle) * Clock.Rate); + RateAdjustedRotation += (float)(Math.Abs(angle) * gameplayClock.TrueGameplayRate); } } } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index d574991fa0..b585a78f42 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; @@ -59,7 +61,7 @@ namespace osu.Game.Rulesets.UI { if (clock != null) { - stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock; + parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; GameplayClock.IsPaused.BindTo(clock.IsPaused); } } @@ -191,7 +193,9 @@ namespace osu.Game.Rulesets.UI private class StabilityGameplayClock : GameplayClock { - public IFrameBasedClock ParentGameplayClock; + public GameplayClock ParentGameplayClock; + + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock.NonGameplayAdjustments; public StabilityGameplayClock(FramedClock underlyingClock) : base(underlyingClock) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4f2cf5005c..45da8816d6 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; @@ -20,6 +22,11 @@ namespace osu.Game.Screens.Play public readonly BindableBool IsPaused = new BindableBool(); + /// + /// All adjustments applied to this clock which don't come from gameplay or mods. + /// + public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; @@ -29,6 +36,23 @@ namespace osu.Game.Screens.Play public double Rate => underlyingClock.Rate; + /// + /// The rate of gameplay when playback is at 100%. + /// This excludes any seeking / user adjustments. + /// + public double TrueGameplayRate + { + get + { + double baseRate = Rate; + + foreach (var adjustment in NonGameplayAdjustments) + baseRate /= adjustment.Value; + + return baseRate; + } + } + public bool IsRunning => underlyingClock.IsRunning; /// diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7a9cb3dddd..d5c3a7232f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -50,8 +51,10 @@ namespace osu.Game.Screens.Play /// /// The final clock which is exposed to underlying components. /// - [Cached] - public readonly GameplayClock GameplayClock; + public GameplayClock GameplayClock => localGameplayClock; + + [Cached(typeof(GameplayClock))] + private readonly LocalGameplayClock localGameplayClock; private Bindable userAudioOffset; @@ -79,7 +82,7 @@ namespace osu.Game.Screens.Play userOffsetClock = new HardwareCorrectionOffsetClock(platformOffsetClock); // the clock to be exposed via DI to children. - GameplayClock = new GameplayClock(userOffsetClock); + localGameplayClock = new LocalGameplayClock(userOffsetClock); GameplayClock.IsPaused.BindTo(IsPaused); } @@ -200,11 +203,26 @@ namespace osu.Game.Screens.Play protected override void Update() { if (!IsPaused.Value) + { userOffsetClock.ProcessFrame(); + } base.Update(); } + private double getTrueGameplayRate() + { + double baseRate = track.Rate; + + if (speedAdjustmentsApplied) + { + baseRate /= UserPlaybackRate.Value; + baseRate /= pauseFreqAdjust.Value; + } + + return baseRate; + } + private bool speedAdjustmentsApplied; private void updateRate() @@ -215,6 +233,9 @@ namespace osu.Game.Screens.Play track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + localGameplayClock.MutableNonGameplayAdjustments.Add(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Add(UserPlaybackRate); + speedAdjustmentsApplied = true; } @@ -231,9 +252,24 @@ namespace osu.Game.Screens.Play track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate); + localGameplayClock.MutableNonGameplayAdjustments.Remove(pauseFreqAdjust); + localGameplayClock.MutableNonGameplayAdjustments.Remove(UserPlaybackRate); + speedAdjustmentsApplied = false; } + public class LocalGameplayClock : GameplayClock + { + public readonly List> MutableNonGameplayAdjustments = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public LocalGameplayClock(FramedOffsetClock underlyingClock) + : base(underlyingClock) + { + } + } + private class HardwareCorrectionOffsetClock : FramedOffsetClock { // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. From 508278505f71a7b72531787364f8ece28f07f9d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 19:40:57 +0900 Subject: [PATCH 003/164] Make local clock private --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index d5c3a7232f..4094de1c4f 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Play speedAdjustmentsApplied = false; } - public class LocalGameplayClock : GameplayClock + private class LocalGameplayClock : GameplayClock { public readonly List> MutableNonGameplayAdjustments = new List>(); From 25bf160d942d8c1bdb6dac7951073a145fe57656 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Sep 2020 22:30:14 +0900 Subject: [PATCH 004/164] Fix missing GameplayClock in some tests --- .../Objects/Drawables/Pieces/SpinnerRotationTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs index e949017ccf..05ed38d241 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerRotationTracker.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private bool rotationTransferred; - [Resolved] + [Resolved(canBeNull: true)] private GameplayClock gameplayClock { get; set; } protected override void Update() @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces currentRotation += angle; // rate has to be applied each frame, because it's not guaranteed to be constant throughout playback // (see: ModTimeRamp) - RateAdjustedRotation += (float)(Math.Abs(angle) * gameplayClock.TrueGameplayRate); + RateAdjustedRotation += (float)(Math.Abs(angle) * (gameplayClock?.TrueGameplayRate ?? Clock.Rate)); } } } From 892d440ed0f337ac47ed192f6db3fdebfbfa5b19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:19:07 +0900 Subject: [PATCH 005/164] Add fallback path for potential null ParentGameplayClock --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index b585a78f42..6716f828ed 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -195,7 +196,7 @@ namespace osu.Game.Rulesets.UI { public GameplayClock ParentGameplayClock; - public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock.NonGameplayAdjustments; + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); public StabilityGameplayClock(FramedClock underlyingClock) : base(underlyingClock) From 26ba7d3100ff77a91cf01ae4737f3c014b7fe5ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Sep 2020 13:20:19 +0900 Subject: [PATCH 006/164] Remove unused method (was moved to a more local location) --- osu.Game/Screens/Play/GameplayClockContainer.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 4094de1c4f..6679e56871 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -210,19 +210,6 @@ namespace osu.Game.Screens.Play base.Update(); } - private double getTrueGameplayRate() - { - double baseRate = track.Rate; - - if (speedAdjustmentsApplied) - { - baseRate /= UserPlaybackRate.Value; - baseRate /= pauseFreqAdjust.Value; - } - - return baseRate; - } - private bool speedAdjustmentsApplied; private void updateRate() From 1c4baa4e2adf718272cede63b8ae0393a3b77ef3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 20:11:27 +0900 Subject: [PATCH 007/164] Add bonus hit results and orderings --- osu.Game/Rulesets/Scoring/HitResult.cs | 103 ++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index b057af2a50..370ffb3a7f 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -2,17 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Game.Utils; namespace osu.Game.Rulesets.Scoring { + [HasOrderedElements] public enum HitResult { /// /// Indicates that the object has not been judged yet. /// [Description(@"")] + [Order(13)] None, + [Order(12)] + Ignore, + /// /// Indicates that the object has been judged as a miss. /// @@ -21,47 +27,142 @@ namespace osu.Game.Rulesets.Scoring /// "too far in the future). It should also define when a forced miss should be triggered (as a result of no user input in time). /// [Description(@"Miss")] + [Order(5)] Miss, [Description(@"Meh")] + [Order(4)] Meh, /// /// Optional judgement. /// [Description(@"OK")] + [Order(3)] Ok, [Description(@"Good")] + [Order(2)] Good, [Description(@"Great")] + [Order(1)] Great, /// /// Optional judgement. /// [Description(@"Perfect")] + [Order(0)] Perfect, /// /// Indicates small tick miss. /// + [Order(11)] SmallTickMiss, /// /// Indicates a small tick hit. /// + [Description(@"S Tick")] + [Order(7)] SmallTickHit, /// /// Indicates a large tick miss. /// + [Order(10)] LargeTickMiss, /// /// Indicates a large tick hit. /// - LargeTickHit + [Description(@"L Tick")] + [Order(6)] + LargeTickHit, + + /// + /// Indicates a small bonus. + /// + [Description("S Bonus")] + [Order(9)] + SmallBonus, + + /// + /// Indicate a large bonus. + /// + [Description("L Bonus")] + [Order(8)] + LargeBonus, + } + + public static class HitResultExtensions + { + /// + /// Whether a affects the combo. + /// + public static bool AffectsCombo(this HitResult result) + { + switch (result) + { + case HitResult.Miss: + case HitResult.Meh: + case HitResult.Ok: + case HitResult.Good: + case HitResult.Great: + case HitResult.Perfect: + case HitResult.LargeTickHit: + case HitResult.LargeTickMiss: + return true; + + default: + return false; + } + } + + /// + /// Whether a should be counted as combo score. + /// + /// + /// This is not the reciprocal of , as and do not affect combo + /// but are still considered as part of the accuracy (not bonus) portion of the score. + /// + public static bool IsBonus(this HitResult result) + { + switch (result) + { + case HitResult.SmallBonus: + case HitResult.LargeBonus: + return true; + + default: + return false; + } + } + + /// + /// Whether a represents a successful hit. + /// + public static bool IsHit(this HitResult result) + { + switch (result) + { + case HitResult.None: + case HitResult.Ignore: + case HitResult.Miss: + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + return false; + + default: + return true; + } + } + + /// + /// Whether a is scorable. + /// + public static bool IsScorable(this HitResult result) => result > HitResult.Ignore; } } From a07597c3691ef68583269ef5e2b41487e64458b8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 20:22:59 +0900 Subject: [PATCH 008/164] Adjust displays to use new results/orderings --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 12 +++-- .../Scores/TopScoreStatisticsSection.cs | 6 +-- osu.Game/Scoring/ScoreInfo.cs | 45 ++++++++++++++++++- .../ContractedPanelMiddleContent.cs | 6 ++- .../Expanded/ExpandedPanelMiddleContent.cs | 5 ++- .../Expanded/Statistics/CounterStatistic.cs | 34 +++++++++++++- .../Expanded/Statistics/HitResultStatistic.cs | 4 +- osu.Game/Tests/TestScoreInfo.cs | 6 +++ 8 files changed, 99 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 56866765b6..edf04dc55a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -92,10 +92,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) }; - foreach (var statistic in score.SortedStatistics.Take(score.SortedStatistics.Count() - 1)) - columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); - - columns.Add(new TableColumn(score.SortedStatistics.LastOrDefault().Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 45, maxSize: 95))); + foreach (var (key, _, _) in score.GetStatisticsForDisplay()) + columns.Add(new TableColumn(key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); if (showPerformancePoints) columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); @@ -148,13 +146,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }; - foreach (var kvp in score.SortedStatistics) + foreach (var (_, value, maxCount) in score.GetStatisticsForDisplay()) { content.Add(new OsuSpriteText { - Text = $"{kvp.Value}", + Text = maxCount == null ? $"{value}" : $"{value}/{maxCount}", Font = OsuFont.GetFont(size: text_size), - Colour = kvp.Value == 0 ? Color4.Gray : Color4.White + Colour = value == 0 ? Color4.Gray : Color4.White }); } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 3a842d0a43..05789e1fc0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -117,7 +117,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ppColumn.Alpha = value.Beatmap?.Status == BeatmapSetOnlineStatus.Ranked ? 1 : 0; ppColumn.Text = $@"{value.PP:N0}"; - statisticsColumns.ChildrenEnumerable = value.SortedStatistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); + statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(s => createStatisticsColumn(s.result, s.count, s.maxCount)); modsColumn.Mods = value.Mods; if (scoreManager != null) @@ -125,9 +125,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private TextColumn createStatisticsColumn(HitResult hitResult, int count) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width) + private TextColumn createStatisticsColumn(HitResult hitResult, int count, int? maxCount) => new TextColumn(hitResult.GetDescription(), smallFont, bottom_columns_min_width) { - Text = count.ToString() + Text = maxCount == null ? $"{count}" : $"{count}/{maxCount}" }; private class InfoColumn : CompositeDrawable diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index efcf1737c9..4ed3f92e25 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; @@ -147,8 +148,6 @@ namespace osu.Game.Scoring [JsonProperty("statistics")] public Dictionary Statistics = new Dictionary(); - public IOrderedEnumerable> SortedStatistics => Statistics.OrderByDescending(pair => pair.Key); - [JsonIgnore] [Column("Statistics")] public string StatisticsJson @@ -186,6 +185,48 @@ namespace osu.Game.Scoring [JsonProperty("position")] public int? Position { get; set; } + public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay() + { + foreach (var key in OrderAttributeUtils.GetValuesInOrder()) + { + if (key.IsBonus()) + continue; + + int value = Statistics.GetOrDefault(key); + + switch (key) + { + case HitResult.SmallTickHit: + { + int total = value + Statistics.GetOrDefault(HitResult.SmallTickMiss); + if (total > 0) + yield return (key, value, total); + + break; + } + + case HitResult.LargeTickHit: + { + int total = value + Statistics.GetOrDefault(HitResult.LargeTickMiss); + if (total > 0) + yield return (key, value, total); + + break; + } + + case HitResult.SmallTickMiss: + case HitResult.LargeTickMiss: + break; + + default: + if (value > 0 || key == HitResult.Miss) + yield return (key, value, null); + + break; + } + } + } + [Serializable] protected class DeserializedMod : IMod { diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 0b85eeafa8..95ece1a9fb 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Users; @@ -116,7 +117,7 @@ namespace osu.Game.Screens.Ranking.Contracted AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) + ChildrenEnumerable = score.GetStatisticsForDisplay().Select(s => createStatistic(s.result, s.count, s.maxCount)) }, new FillFlowContainer { @@ -198,6 +199,9 @@ namespace osu.Game.Screens.Ranking.Contracted }; } + private Drawable createStatistic(HitResult result, int count, int? maxCount) + => createStatistic(result.GetDescription(), maxCount == null ? $"{count}" : $"{count}/{maxCount}"); + private Drawable createStatistic(string key, string value) => new Container { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 0033cd1f43..ebab8c88f6 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -65,8 +65,9 @@ namespace osu.Game.Screens.Ranking.Expanded }; var bottomStatistics = new List(); - foreach (var stat in score.SortedStatistics) - bottomStatistics.Add(new HitResultStatistic(stat.Key, stat.Value)); + + foreach (var (key, value, maxCount) in score.GetStatisticsForDisplay()) + bottomStatistics.Add(new HitResultStatistic(key, value, maxCount)); statisticDisplays.AddRange(topStatistics); statisticDisplays.AddRange(bottomStatistics); diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs index e820831809..fc01f5e9c4 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -16,6 +17,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics public class CounterStatistic : StatisticDisplay { private readonly int count; + private readonly int? maxCount; private RollingCounter counter; @@ -24,10 +26,12 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics /// /// The name of the statistic. /// The value to display. - public CounterStatistic(string header, int count) + /// The maximum value of . Not displayed if null. + public CounterStatistic(string header, int count, int? maxCount = null) : base(header) { this.count = count; + this.maxCount = maxCount; } public override void Appear() @@ -36,7 +40,33 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics counter.Current.Value = count; } - protected override Drawable CreateContent() => counter = new Counter(); + protected override Drawable CreateContent() + { + var container = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Child = counter = new Counter + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + }; + + if (maxCount != null) + { + container.Add(new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Torus.With(size: 12, fixedWidth: true), + Spacing = new Vector2(-2, 0), + Text = $"/{maxCount}" + }); + } + + return container; + } private class Counter : RollingCounter { diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs index faa4a6a96c..a86033713f 100644 --- a/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs +++ b/osu.Game/Screens/Ranking/Expanded/Statistics/HitResultStatistic.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics { private readonly HitResult result; - public HitResultStatistic(HitResult result, int count) - : base(result.GetDescription(), count) + public HitResultStatistic(HitResult result, int count, int? maxCount = null) + : base(result.GetDescription(), count, maxCount) { this.result = result; } diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 31cced6ce4..704d01e479 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -37,6 +37,12 @@ namespace osu.Game.Tests Statistics[HitResult.Meh] = 50; Statistics[HitResult.Good] = 100; Statistics[HitResult.Great] = 300; + Statistics[HitResult.SmallTickHit] = 50; + Statistics[HitResult.SmallTickMiss] = 25; + Statistics[HitResult.LargeTickHit] = 100; + Statistics[HitResult.LargeTickMiss] = 50; + Statistics[HitResult.SmallBonus] = 10; + Statistics[HitResult.SmallBonus] = 50; Position = 1; } From 2517fffb7e61b4bad9f0f9da41a834d1c42a14d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 20:48:16 +0900 Subject: [PATCH 009/164] Fix incorrect display in beatmap overlay table --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index edf04dc55a..968355c377 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -11,9 +11,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Scoring; using osu.Game.Users.Drawables; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -55,6 +57,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores highAccuracyColour = colours.GreenLight; } + /// + /// The statistics that appear in the table, in order of appearance. + /// + private readonly List statisticResultTypes = new List(); + private bool showPerformancePoints; public void DisplayScores(IReadOnlyList scores, bool showPerformanceColumn) @@ -65,11 +72,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores return; showPerformancePoints = showPerformanceColumn; + statisticResultTypes.Clear(); for (int i = 0; i < scores.Count; i++) backgroundFlow.Add(new ScoreTableRowBackground(i, scores[i], row_height)); - Columns = createHeaders(scores.FirstOrDefault()); + Columns = createHeaders(scores); Content = scores.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); } @@ -79,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores backgroundFlow.Clear(); } - private TableColumn[] createHeaders(ScoreInfo score) + private TableColumn[] createHeaders(IReadOnlyList scores) { var columns = new List { @@ -92,8 +100,17 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) }; - foreach (var (key, _, _) in score.GetStatisticsForDisplay()) - columns.Add(new TableColumn(key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + // All statistics across all scores, unordered. + var allScoreStatistics = scores.SelectMany(s => s.GetStatisticsForDisplay().Select(stat => stat.result)).ToHashSet(); + + foreach (var result in OrderAttributeUtils.GetValuesInOrder()) + { + if (!allScoreStatistics.Contains(result)) + continue; + + columns.Add(new TableColumn(result.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + statisticResultTypes.Add(result); + } if (showPerformancePoints) columns.Add(new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 30))); @@ -146,13 +163,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } }; - foreach (var (_, value, maxCount) in score.GetStatisticsForDisplay()) + var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.result); + + foreach (var result in statisticResultTypes) { + if (!availableStatistics.TryGetValue(result, out var stat)) + stat = (result, 0, null); + content.Add(new OsuSpriteText { - Text = maxCount == null ? $"{value}" : $"{value}/{maxCount}", + Text = stat.maxCount == null ? $"{stat.count}" : $"{stat.count}/{stat.maxCount}", Font = OsuFont.GetFont(size: text_size), - Colour = value == 0 ? Color4.Gray : Color4.White + Colour = stat.count == 0 ? Color4.Gray : Color4.White }); } From 4bcc3ca82883588cd152a1d8efa58e5a60711b76 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 25 Sep 2020 22:16:14 +0900 Subject: [PATCH 010/164] Add AffectsAccuracy extension --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 370ffb3a7f..1de62cf8e5 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Scoring public static class HitResultExtensions { /// - /// Whether a affects the combo. + /// Whether a increases/decreases the combo, and affects the combo portion of the score. /// public static bool AffectsCombo(this HitResult result) { @@ -122,12 +122,14 @@ namespace osu.Game.Rulesets.Scoring } /// - /// Whether a should be counted as combo score. + /// Whether a affects the accuracy portion of the score. + /// + public static bool AffectsAccuracy(this HitResult result) + => IsScorable(result) && !IsBonus(result); + + /// + /// Whether a should be counted as bonus score. /// - /// - /// This is not the reciprocal of , as and do not affect combo - /// but are still considered as part of the accuracy (not bonus) portion of the score. - /// public static bool IsBonus(this HitResult result) { switch (result) From e7d04564545cb4fe87d04add0792d6d14c1284c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sat, 26 Sep 2020 16:25:17 +0200 Subject: [PATCH 011/164] Add SpinnerNoBlink to LegacySettings --- osu.Game/Skinning/LegacySkinConfiguration.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index 1d5412d93f..a0e8fb2f92 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -19,6 +19,7 @@ namespace osu.Game.Skinning ComboOverlap, AnimationFramerate, LayeredHitSounds, + SpinnerNoBlink } } } From 33d000e5325af0ca813c9c26de761761332f51b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Sat, 26 Sep 2020 16:25:57 +0200 Subject: [PATCH 012/164] Add support for SpinnerNoBlink in legacy spinner --- .../Skinning/LegacyOldStyleSpinner.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index e157842fd1..ada3a825d0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; +using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning { @@ -25,12 +26,16 @@ namespace osu.Game.Rulesets.Osu.Skinning private Sprite metreSprite; private Container metre; + private bool spinnerNoBlink; + private const float sprite_scale = 1 / 1.6f; private const float final_metre_height = 692 * sprite_scale; [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { + spinnerNoBlink = source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? false; + drawableSpinner = (DrawableSpinner)drawableObject; RelativeSizeAxes = Axes.Both; @@ -117,12 +122,15 @@ namespace osu.Game.Rulesets.Osu.Skinning private float getMetreHeight(float progress) { - progress = Math.Min(99, progress * 100); + progress *= 100; + + // the spinner should still blink at 100% progress. + if (!spinnerNoBlink) + progress = Math.Min(99, progress); int barCount = (int)progress / 10; - // todo: add SpinnerNoBlink support - if (RNG.NextBool(((int)progress % 10) / 10f)) + if (!spinnerNoBlink && RNG.NextBool(((int)progress % 10) / 10f)) barCount++; return (float)barCount / total_bars * final_metre_height; From deb207001a8fc34feea461fd6cc6d7f275b0df27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 15:23:34 +0200 Subject: [PATCH 013/164] Remove schedule causing default skin explosion regression --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 7b3fbb1faf..120cf264c3 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -215,16 +215,12 @@ namespace osu.Game.Rulesets.Taiko.UI private void addDrumRollHit(DrawableDrumRollTick drawableTick) => drumRollHitContainer.Add(new DrawableFlyingHit(drawableTick)); - /// - /// As legacy skins have different explosions for singular and double strong hits, - /// explosion addition is scheduled to ensure that both hits are processed if they occur on the same frame. - /// - private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) => Schedule(() => + private void addExplosion(DrawableHitObject drawableObject, HitResult result, HitType type) { hitExplosionContainer.Add(new HitExplosion(drawableObject, result)); if (drawableObject.HitObject.Kiai) kiaiExplosionContainer.Add(new KiaiHitExplosion(drawableObject, type)); - }); + } private class ProxyContainer : LifetimeManagementContainer { From d5f1d94b517703f202bfcf6dfe695eb46ee02f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 15:29:04 +0200 Subject: [PATCH 014/164] Allow specifying two sprites for legacy hit explosions --- .../Skinning/LegacyHitExplosion.cs | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index b5ec2e8def..ca0a8f601c 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,14 +9,36 @@ namespace osu.Game.Rulesets.Taiko.Skinning { public class LegacyHitExplosion : CompositeDrawable { - public LegacyHitExplosion(Drawable sprite) - { - InternalChild = sprite; + private readonly Drawable sprite; + private readonly Drawable strongSprite; + /// + /// Creates a new legacy hit explosion. + /// + /// + /// Contrary to stable's, this implementation doesn't require a frame-perfect hit + /// for the strong sprite to be displayed. + /// + /// The normal legacy explosion sprite. + /// The strong legacy explosion sprite. + public LegacyHitExplosion(Drawable sprite, Drawable strongSprite = null) + { + this.sprite = sprite; + this.strongSprite = strongSprite; + } + + [BackgroundDependencyLoader] + private void load() + { Anchor = Anchor.Centre; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; + + AddInternal(sprite); + + if (strongSprite != null) + AddInternal(strongSprite.With(s => s.Alpha = 0)); } protected override void LoadComplete() From eb62ad4e551e72c3642ba56db21c954a4c850c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 15:33:18 +0200 Subject: [PATCH 015/164] Look up both sprites for legacy explosions --- .../Skinning/TaikoLegacySkinTransformer.cs | 30 ++++++++++--------- .../TaikoSkinComponents.cs | 2 -- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 27 ++++------------- 3 files changed, 21 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index c222ccb51f..d320b824e6 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -74,15 +74,23 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; - case TaikoSkinComponents.TaikoExplosionGood: - case TaikoSkinComponents.TaikoExplosionGoodStrong: - case TaikoSkinComponents.TaikoExplosionGreat: - case TaikoSkinComponents.TaikoExplosionGreatStrong: case TaikoSkinComponents.TaikoExplosionMiss: - var sprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); - if (sprite != null) - return new LegacyHitExplosion(sprite); + var missSprite = this.GetAnimation(getHitName(taikoComponent.Component), true, false); + if (missSprite != null) + return new LegacyHitExplosion(missSprite); + + return null; + + case TaikoSkinComponents.TaikoExplosionGood: + case TaikoSkinComponents.TaikoExplosionGreat: + + var hitName = getHitName(taikoComponent.Component); + var hitSprite = this.GetAnimation(hitName, true, false); + var strongHitSprite = this.GetAnimation($"{hitName}k", true, false); + + if (hitSprite != null) + return new LegacyHitExplosion(hitSprite, strongHitSprite); return null; @@ -109,17 +117,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionGood: return "taiko-hit100"; - case TaikoSkinComponents.TaikoExplosionGoodStrong: - return "taiko-hit100k"; - case TaikoSkinComponents.TaikoExplosionGreat: return "taiko-hit300"; - - case TaikoSkinComponents.TaikoExplosionGreatStrong: - return "taiko-hit300k"; } - throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type"); + throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}"); } public override SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 0d785adb4a..ac4fb51661 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -17,9 +17,7 @@ namespace osu.Game.Rulesets.Taiko BarLine, TaikoExplosionMiss, TaikoExplosionGood, - TaikoExplosionGoodStrong, TaikoExplosionGreat, - TaikoExplosionGreatStrong, Scroller, Mascot, } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index efd1b25046..53765f04dd 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.UI @@ -50,10 +48,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load() { - Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(JudgedObject)), _ => new DefaultHitExplosion(JudgedObject, result)); + Child = skinnable = new SkinnableDrawable(new TaikoSkinComponent(getComponentName(result)), _ => new DefaultHitExplosion(JudgedObject, result)); } - private TaikoSkinComponents getComponentName(DrawableHitObject judgedObject) + private static TaikoSkinComponents getComponentName(HitResult result) { switch (result) { @@ -61,28 +59,13 @@ namespace osu.Game.Rulesets.Taiko.UI return TaikoSkinComponents.TaikoExplosionMiss; case HitResult.Good: - return useStrongExplosion(judgedObject) - ? TaikoSkinComponents.TaikoExplosionGoodStrong - : TaikoSkinComponents.TaikoExplosionGood; + return TaikoSkinComponents.TaikoExplosionGood; case HitResult.Great: - return useStrongExplosion(judgedObject) - ? TaikoSkinComponents.TaikoExplosionGreatStrong - : TaikoSkinComponents.TaikoExplosionGreat; + return TaikoSkinComponents.TaikoExplosionGreat; } - throw new ArgumentOutOfRangeException(nameof(judgedObject), "Invalid result type"); - } - - private bool useStrongExplosion(DrawableHitObject judgedObject) - { - if (!(judgedObject.HitObject is Hit)) - return false; - - if (!(judgedObject.NestedHitObjects.SingleOrDefault() is DrawableStrongNestedHit nestedHit)) - return false; - - return judgedObject.Result.Type == nestedHit.Result.Type; + throw new ArgumentOutOfRangeException(nameof(result), $"Invalid result type: {result}"); } /// From 2f7c0b49344127ea1ccbe286f021edc71285352b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 16:07:19 +0200 Subject: [PATCH 016/164] Allow switching between legacy sprites --- .../Skinning/LegacyHitExplosion.cs | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index ca0a8f601c..0f91239819 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects.Drawables; namespace osu.Game.Rulesets.Taiko.Skinning { @@ -12,6 +15,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning private readonly Drawable sprite; private readonly Drawable strongSprite; + private DrawableHit hit; + private DrawableStrongNestedHit nestedStrongHit; + /// /// Creates a new legacy hit explosion. /// @@ -28,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning } [BackgroundDependencyLoader] - private void load() + private void load(DrawableHitObject judgedObject) { Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -39,6 +45,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (strongSprite != null) AddInternal(strongSprite.With(s => s.Alpha = 0)); + + if (judgedObject is DrawableHit h) + { + hit = h; + nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit; + } } protected override void LoadComplete() @@ -56,5 +68,24 @@ namespace osu.Game.Rulesets.Taiko.Skinning Expire(true); } + + protected override void Update() + { + base.Update(); + + if (shouldSwitchToStrongSprite() && strongSprite != null) + { + sprite.FadeOut(50, Easing.OutQuint); + strongSprite.FadeIn(50, Easing.OutQuint); + } + } + + private bool shouldSwitchToStrongSprite() + { + if (hit == null || nestedStrongHit == null) + return false; + + return hit.Result.Type == nestedStrongHit.Result.Type; + } } } From 49441286311c0c052a1100c13324b9c5b5f90f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 27 Sep 2020 18:11:12 +0200 Subject: [PATCH 017/164] Ensure both sprites are centered --- .../Skinning/LegacyHitExplosion.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index 0f91239819..f24f75f097 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -41,10 +41,21 @@ namespace osu.Game.Rulesets.Taiko.Skinning AutoSizeAxes = Axes.Both; - AddInternal(sprite); + AddInternal(sprite.With(s => + { + s.Anchor = Anchor.Centre; + s.Origin = Anchor.Centre; + })); if (strongSprite != null) - AddInternal(strongSprite.With(s => s.Alpha = 0)); + { + AddInternal(strongSprite.With(s => + { + s.Alpha = 0; + s.Anchor = Anchor.Centre; + s.Origin = Anchor.Centre; + })); + } if (judgedObject is DrawableHit h) { From 4f0c0ea5f9bb4d3dc7b671af349cf6ab87d15313 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Sep 2020 18:16:19 +0900 Subject: [PATCH 018/164] Fix hit samples playing while paused / seeking in the editor --- .../Objects/Drawables/DrawableHitObject.cs | 4 ++-- osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/EditorClock.cs | 22 +++++++++++++++++-- osu.Game/Screens/Play/GameplayClock.cs | 2 +- osu.Game/Screens/Play/ISeekableClock.cs | 13 +++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Play/ISeekableClock.cs diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7c05bc9aa7..5b26607bf7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -360,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } + private ISeekableClock seekableClock { get; set; } /// /// Calculate the position to be used for sample playback at a specified X position (0..1). @@ -377,7 +377,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Whether samples should currently be playing. Will be false during seek operations. /// - protected bool ShouldPlaySamples => gameplayClock?.IsSeeking != true; + protected bool ShouldPlaySamples => seekableClock?.IsSeeking != true; /// /// Plays all the hit sounds for this . diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index fd090e0959..1f5e261588 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -107,6 +107,7 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); + dependencies.CacheAs(clock); AddInternal(clock); // todo: remove caching of this and consume via editorBeatmap? diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index ec203df064..ebc73c2bb8 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -7,17 +7,18 @@ using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; -using osu.Framework.Utils; using osu.Framework.Timing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISeekableClock { public IBindable Track => track; @@ -211,8 +212,25 @@ namespace osu.Game.Screens.Edit private const double transform_time = 300; + public bool IsSeeking { get; private set; } + + protected override void Update() + { + base.Update(); + + if (IsSeeking) + { + // we are either running a seek tween or doing an immediate seek. + // in the case of an immediate seek the seeking bool will be set to false after one update. + // this allows for silencing hit sounds and the likes. + IsSeeking = Transforms.Any(); + } + } + public void SeekTo(double seekDestination) { + IsSeeking = true; + if (IsRunning) Seek(seekDestination); else diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 4f2cf5005c..b10e50882c 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock + public class GameplayClock : IFrameBasedClock, ISeekableClock { private readonly IFrameBasedClock underlyingClock; diff --git a/osu.Game/Screens/Play/ISeekableClock.cs b/osu.Game/Screens/Play/ISeekableClock.cs new file mode 100644 index 0000000000..9d992a45fd --- /dev/null +++ b/osu.Game/Screens/Play/ISeekableClock.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Play +{ + public interface ISeekableClock + { + /// + /// Whether an ongoing seek operation is active. + /// + bool IsSeeking { get; } + } +} From 40a4654ef91c2ec9f92cf8b944eb48edd1ab9972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Mon, 28 Sep 2020 12:21:43 +0200 Subject: [PATCH 019/164] Invert spinnerNoBlink to spinnerBlink locally --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index ada3a825d0..cce50b24ac 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private Sprite metreSprite; private Container metre; - private bool spinnerNoBlink; + private bool spinnerBlink; private const float sprite_scale = 1 / 1.6f; private const float final_metre_height = 692 * sprite_scale; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerNoBlink = source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? false; + spinnerBlink = !source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? true; drawableSpinner = (DrawableSpinner)drawableObject; @@ -125,12 +125,12 @@ namespace osu.Game.Rulesets.Osu.Skinning progress *= 100; // the spinner should still blink at 100% progress. - if (!spinnerNoBlink) + if (spinnerBlink) progress = Math.Min(99, progress); int barCount = (int)progress / 10; - if (!spinnerNoBlink && RNG.NextBool(((int)progress % 10) / 10f)) + if (spinnerBlink && RNG.NextBool(((int)progress % 10) / 10f)) barCount++; return (float)barCount / total_bars * final_metre_height; From 54852991f36f2136dd5240058e5db758f81e65ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Mon, 28 Sep 2020 12:24:30 +0200 Subject: [PATCH 020/164] Move SpinnerNoBlink to OsuSkinConfiguration --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 3 +-- osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs | 3 ++- osu.Game/Skinning/LegacySkinConfiguration.cs | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index cce50b24ac..63cd48676e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -12,7 +12,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Skinning; using osuTK; -using static osu.Game.Skinning.LegacySkinConfiguration; namespace osu.Game.Rulesets.Osu.Skinning { @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerBlink = !source.GetConfig(LegacySetting.SpinnerNoBlink)?.Value ?? true; + spinnerBlink = !source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value ?? true; drawableSpinner = (DrawableSpinner)drawableObject; diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs index e034e14eb0..63c9b53278 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning CursorRotate, HitCircleOverlayAboveNumber, HitCircleOverlayAboveNumer, // Some old skins will have this typo - SpinnerFrequencyModulate + SpinnerFrequencyModulate, + SpinnerNoBlink } } diff --git a/osu.Game/Skinning/LegacySkinConfiguration.cs b/osu.Game/Skinning/LegacySkinConfiguration.cs index a0e8fb2f92..828804b9cb 100644 --- a/osu.Game/Skinning/LegacySkinConfiguration.cs +++ b/osu.Game/Skinning/LegacySkinConfiguration.cs @@ -18,8 +18,7 @@ namespace osu.Game.Skinning ComboPrefix, ComboOverlap, AnimationFramerate, - LayeredHitSounds, - SpinnerNoBlink + LayeredHitSounds } } } From 0900661b23e0cd3aea39142d551d442e8f084c91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Sep 2020 16:34:04 +0200 Subject: [PATCH 021/164] Use IsHit for strong hit instead of checking result type --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index f24f75f097..58dfa1747a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (hit == null || nestedStrongHit == null) return false; - return hit.Result.Type == nestedStrongHit.Result.Type; + return nestedStrongHit.IsHit; } } } From f6f267a43a1651deaddb1f2a013b6efdde043a99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Sep 2020 16:38:30 +0200 Subject: [PATCH 022/164] Switch to strong sprite exactly once --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index 58dfa1747a..cd5c9c757f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -17,6 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning private DrawableHit hit; private DrawableStrongNestedHit nestedStrongHit; + private bool switchedToStrongSprite; /// /// Creates a new legacy hit explosion. @@ -84,16 +85,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning { base.Update(); - if (shouldSwitchToStrongSprite() && strongSprite != null) + if (shouldSwitchToStrongSprite() && !switchedToStrongSprite) { sprite.FadeOut(50, Easing.OutQuint); strongSprite.FadeIn(50, Easing.OutQuint); + switchedToStrongSprite = true; } } private bool shouldSwitchToStrongSprite() { - if (hit == null || nestedStrongHit == null) + if (hit == null || nestedStrongHit == null || strongSprite == null) return false; return nestedStrongHit.IsHit; From 2fb9a5d7342e63569dff7705450539ece330736c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 28 Sep 2020 16:59:33 +0200 Subject: [PATCH 023/164] Remove no longer required field --- osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs index cd5c9c757f..19493271be 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHitExplosion.cs @@ -15,7 +15,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning private readonly Drawable sprite; private readonly Drawable strongSprite; - private DrawableHit hit; private DrawableStrongNestedHit nestedStrongHit; private bool switchedToStrongSprite; @@ -58,11 +57,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning })); } - if (judgedObject is DrawableHit h) - { - hit = h; + if (judgedObject is DrawableHit hit) nestedStrongHit = hit.NestedHitObjects.SingleOrDefault() as DrawableStrongNestedHit; - } } protected override void LoadComplete() @@ -95,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning private bool shouldSwitchToStrongSprite() { - if (hit == null || nestedStrongHit == null || strongSprite == null) + if (nestedStrongHit == null || strongSprite == null) return false; return nestedStrongHit.IsHit; From 585b857a0c7f417a5fdfeb877d032324b4879f13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 12:17:38 +0900 Subject: [PATCH 024/164] Handle paused state correctly --- osu.Game/Screens/Edit/EditorClock.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index ebc73c2bb8..99e5044b1f 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -220,10 +220,12 @@ namespace osu.Game.Screens.Edit if (IsSeeking) { + bool isPaused = track.Value?.IsRunning != true; + // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - IsSeeking = Transforms.Any(); + IsSeeking = isPaused || Transforms.Any(); } } From d6f3beffb648f1a0e059a5d641984522c799b77b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 12:45:20 +0900 Subject: [PATCH 025/164] Use existing bindable flow instead --- .../Objects/Drawables/DrawableSlider.cs | 5 +-- .../Objects/Drawables/DrawableSpinner.cs | 5 +-- .../Gameplay/TestSceneSkinnableSound.cs | 2 +- .../Objects/Drawables/DrawableHitObject.cs | 9 ++---- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorClock.cs | 29 +++++++++++++---- osu.Game/Screens/Play/GameplayClock.cs | 4 ++- .../Screens/Play/ISamplePlaybackDisabler.cs | 20 ++++++++++++ osu.Game/Screens/Play/ISeekableClock.cs | 13 -------- osu.Game/Skinning/SkinnableSound.cs | 32 +++++++++++-------- 10 files changed, 70 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Screens/Play/ISamplePlaybackDisabler.cs delete mode 100644 osu.Game/Screens/Play/ISeekableClock.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 07f40f763b..68f203db47 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -112,10 +112,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updateSlidingSample(ValueChangedEvent tracking) { - // note that samples will not start playing if exiting a seek operation in the middle of a slider. - // may be something we want to address at a later point, but not so easy to make happen right now - // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). - if (tracking.NewValue && ShouldPlaySamples) + if (tracking.NewValue) slidingSample?.Play(); else slidingSample?.Stop(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a57bb466c7..b2a706833c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -113,10 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private void updateSpinningSample(ValueChangedEvent tracking) { - // note that samples will not start playing if exiting a seek operation in the middle of a spinner. - // may be something we want to address at a later point, but not so easy to make happen right now - // (SkinnableSound would need to expose whether the sample is already playing and this logic would need to run in Update). - if (tracking.NewValue && ShouldPlaySamples) + if (tracking.NewValue) { spinningSample?.Play(); spinningSample?.VolumeTo(1, 200); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index ed75d83151..8b37cbd06f 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestSceneSkinnableSound : OsuTestScene { - [Cached] + [Cached(typeof(ISamplePlaybackDisabler))] private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private TestSkinSourceContainer skinSource; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5b26607bf7..796b8f7aae 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -360,7 +360,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } [Resolved(canBeNull: true)] - private ISeekableClock seekableClock { get; set; } + private ISamplePlaybackDisabler samplePlaybackDisabler { get; set; } /// /// Calculate the position to be used for sample playback at a specified X position (0..1). @@ -374,18 +374,13 @@ namespace osu.Game.Rulesets.Objects.Drawables return balance_adjust_amount * (userPositionalHitSounds.Value ? position - 0.5f : 0); } - /// - /// Whether samples should currently be playing. Will be false during seek operations. - /// - protected bool ShouldPlaySamples => seekableClock?.IsSeeking != true; - /// /// Plays all the hit sounds for this . /// This is invoked automatically when this is hit. /// public virtual void PlaySamples() { - if (Samples != null && ShouldPlaySamples) + if (Samples != null) { Samples.Balance.Value = CalculateSamplePlaybackBalance(SamplePlaybackPosition); Samples.Play(); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1f5e261588..a0692d94e6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit UpdateClockSource(); dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + dependencies.CacheAs(clock); AddInternal(clock); // todo: remove caching of this and consume via editorBeatmap? diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 99e5044b1f..4b7cd82637 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISeekableClock + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock, ISamplePlaybackDisabler { public IBindable Track => track; @@ -32,6 +32,10 @@ namespace osu.Game.Screens.Edit private readonly DecoupleableInterpolatingFramedClock underlyingClock; + public IBindable SamplePlaybackDisabled => samplePlaybackDisabled; + + private readonly Bindable samplePlaybackDisabled = new Bindable(); + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) { @@ -167,11 +171,14 @@ namespace osu.Game.Screens.Edit public void Stop() { + samplePlaybackDisabled.Value = true; underlyingClock.Stop(); } public bool Seek(double position) { + samplePlaybackDisabled.Value = true; + ClearTransforms(); return underlyingClock.Seek(position); } @@ -212,26 +219,34 @@ namespace osu.Game.Screens.Edit private const double transform_time = 300; - public bool IsSeeking { get; private set; } - protected override void Update() { base.Update(); - if (IsSeeking) + updateSeekingState(); + } + + private void updateSeekingState() + { + if (samplePlaybackDisabled.Value) { - bool isPaused = track.Value?.IsRunning != true; + if (track.Value?.IsRunning != true) + { + // seeking in the editor can happen while the track isn't running. + // in this case we always want to expose ourselves as seeking (to avoid sample playback). + return; + } // we are either running a seek tween or doing an immediate seek. // in the case of an immediate seek the seeking bool will be set to false after one update. // this allows for silencing hit sounds and the likes. - IsSeeking = isPaused || Transforms.Any(); + samplePlaybackDisabled.Value = Transforms.Any(); } } public void SeekTo(double seekDestination) { - IsSeeking = true; + samplePlaybackDisabled.Value = true; if (IsRunning) Seek(seekDestination); diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index b10e50882c..da4648fd2b 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play /// , as this should only be done once to ensure accuracy. /// /// - public class GameplayClock : IFrameBasedClock, ISeekableClock + public class GameplayClock : IFrameBasedClock, ISamplePlaybackDisabler { private readonly IFrameBasedClock underlyingClock; @@ -48,5 +48,7 @@ namespace osu.Game.Screens.Play public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; public IClock Source => underlyingClock; + + public IBindable SamplePlaybackDisabled => IsPaused; } } diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs new file mode 100644 index 0000000000..83e89d654b --- /dev/null +++ b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play +{ + /// + /// Allows a component to disable sample playback dynamically as required. + /// Handled by . + /// + public interface ISamplePlaybackDisabler + { + /// + /// Whether sample playback should be disabled (or paused for looping samples). + /// + IBindable SamplePlaybackDisabled { get; } + } +} diff --git a/osu.Game/Screens/Play/ISeekableClock.cs b/osu.Game/Screens/Play/ISeekableClock.cs deleted file mode 100644 index 9d992a45fd..0000000000 --- a/osu.Game/Screens/Play/ISeekableClock.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Screens.Play -{ - public interface ISeekableClock - { - /// - /// Whether an ongoing seek operation is active. - /// - bool IsSeeking { get; } - } -} diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index ba14049b41..704ba099c1 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -50,25 +50,28 @@ namespace osu.Game.Skinning InternalChild = samplesContainer = new AudioContainer(); } - private Bindable gameplayClockPaused; + private readonly IBindable samplePlaybackDisabled = new Bindable(); [BackgroundDependencyLoader(true)] - private void load(GameplayClock gameplayClock) + private void load(ISamplePlaybackDisabler samplePlaybackDisabler) { // if in a gameplay context, pause sample playback when gameplay is paused. - gameplayClockPaused = gameplayClock?.IsPaused.GetBoundCopy(); - gameplayClockPaused?.BindValueChanged(paused => + if (samplePlaybackDisabler != null) { - if (requestedPlaying) + samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + samplePlaybackDisabled.BindValueChanged(disabled => { - if (paused.NewValue) - stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) - play(); - } - }); + if (requestedPlaying) + { + if (disabled.NewValue) + stop(); + // it's not easy to know if a sample has finished playing (to end). + // to keep things simple only resume playing looping samples. + else if (Looping) + play(); + } + }); + } } private bool looping; @@ -94,6 +97,9 @@ namespace osu.Game.Skinning private void play() { + if (samplePlaybackDisabled.Value) + return; + samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) From c5f6b77bbaa03be219cf5978391852ff5019287a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 13:42:17 +0900 Subject: [PATCH 026/164] Add missing cached type --- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7a9cb3dddd..cc25a733f1 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -51,6 +51,7 @@ namespace osu.Game.Screens.Play /// The final clock which is exposed to underlying components. /// [Cached] + [Cached(typeof(ISamplePlaybackDisabler))] public readonly GameplayClock GameplayClock; private Bindable userAudioOffset; From 74e74e1c31acdd58bfb258ca6a3dc0b291c548e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 14:20:41 +0900 Subject: [PATCH 027/164] Fix pause loop sound not working because paused --- osu.Game/Screens/Play/PauseOverlay.cs | 12 +++++++++++- osu.Game/Skinning/SkinnableSound.cs | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 65f34aba3e..9494971f8a 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) + AddInternal(pauseLoop = new UnpausableSkinnableSound(new SampleInfo("pause-loop")) { Looping = true, Volume = { Value = 0 } @@ -54,5 +54,15 @@ namespace osu.Game.Screens.Play pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } + + private class UnpausableSkinnableSound : SkinnableSound + { + protected override bool PlayWhenPaused => true; + + public UnpausableSkinnableSound(SampleInfo sampleInfo) + : base(sampleInfo) + { + } + } } } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 704ba099c1..f3ab8b86bc 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -37,6 +37,8 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; + protected virtual bool PlayWhenPaused => false; + private readonly AudioContainer samplesContainer; public SkinnableSound(ISampleInfo hitSamples) @@ -63,7 +65,7 @@ namespace osu.Game.Skinning { if (requestedPlaying) { - if (disabled.NewValue) + if (disabled.NewValue && !PlayWhenPaused) stop(); // it's not easy to know if a sample has finished playing (to end). // to keep things simple only resume playing looping samples. @@ -97,7 +99,7 @@ namespace osu.Game.Skinning private void play() { - if (samplePlaybackDisabled.Value) + if (samplePlaybackDisabled.Value && !PlayWhenPaused) return; samplesContainer.ForEach(c => From 136843c8e450507ad0527622af851d648afb1545 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 14:09:51 +0900 Subject: [PATCH 028/164] Make DrawableStoryboardSample a SkinnableSound Allows sharing pause logic with gameplay samples. --- .../Gameplay/TestSceneStoryboardSamples.cs | 15 ++-- osu.Game/Rulesets/Mods/IApplicableToSample.cs | 4 +- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 4 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 6 +- osu.Game/Screens/Play/Player.cs | 80 ++++++++++--------- osu.Game/Skinning/SkinnableSound.cs | 39 ++++----- .../Drawables/DrawableStoryboardSample.cs | 41 ++++------ 7 files changed, 94 insertions(+), 95 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index a690eb3b59..d46769a7c0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Audio; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Audio; @@ -106,9 +108,14 @@ namespace osu.Game.Tests.Gameplay Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); SelectedMods.Value = new[] { testedMod }; - Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0)); + var beatmapSkinSourceContainer = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); - gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0) + { + Child = beatmapSkinSourceContainer + }); + + beatmapSkinSourceContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { Clock = gameplayContainer.GameplayClock }); @@ -116,7 +123,7 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); + AddAssert("sample playback rate matches mod rates", () => sample.ChildrenOfType().First().AggregateFrequency.Value == expectedRate); } private class TestSkin : LegacySkin @@ -168,8 +175,6 @@ namespace osu.Game.Tests.Gameplay : base(sampleInfo) { } - - public new SampleChannel Channel => base.Channel; } } } diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs index 559d127cfc..50a6d501b6 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToSample.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Audio; namespace osu.Game.Rulesets.Mods { @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToSample : IApplicableMod { - void ApplyToSample(SampleChannel sample); + void ApplyToSample(DrawableSample sample); } } diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index fec21764b0..2150b0fb68 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Graphics.Audio; namespace osu.Game.Rulesets.Mods { @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } - public virtual void ApplyToSample(SampleChannel sample) + public virtual void ApplyToSample(DrawableSample sample) { sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 20c8d0f3e7..4d43ae73d3 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -6,11 +6,11 @@ using System.Linq; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Graphics.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; -using osu.Framework.Audio.Sample; +using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods { @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.TriggerChange(); } - public void ApplyToSample(SampleChannel sample) + public void ApplyToSample(DrawableSample sample) { sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 8e2ed583f2..175722c44e 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -191,9 +191,25 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(gameplayBeatmap); - addUnderlayComponents(GameplayClockContainer); - addGameplayComponents(GameplayClockContainer, Beatmap.Value, playableBeatmap); - addOverlayComponents(GameplayClockContainer, Beatmap.Value); + var beatmapSkinProvider = new BeatmapSkinProvidingContainer(Beatmap.Value.Skin); + + // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation + // full access to all skin sources. + var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); + + // load the skinning hierarchy first. + // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. + GameplayClockContainer.Add(beatmapSkinProvider.WithChild(rulesetSkinProvider)); + + rulesetSkinProvider.AddRange(new[] + { + // underlay and gameplay should have access the to skinning sources. + createUnderlayComponents(), + createGameplayComponents(Beatmap.Value, playableBeatmap) + }); + + // add the overlay components as a separate step as they proxy some elements from the above underlay/gameplay components. + GameplayClockContainer.Add(createOverlayComponents(Beatmap.Value)); if (!DrawableRuleset.AllowGameplayOverlays) { @@ -238,45 +254,31 @@ namespace osu.Game.Screens.Play breakTracker.IsBreakTime.BindValueChanged(onBreakTimeChanged, true); } - private void addUnderlayComponents(Container target) + private Drawable createUnderlayComponents() => + DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }; + + private Drawable createGameplayComponents(WorkingBeatmap working, IBeatmap playableBeatmap) => new ScalingContainer(ScalingMode.Gameplay) { - target.Add(DimmableStoryboard = new DimmableStoryboard(Beatmap.Value.Storyboard) { RelativeSizeAxes = Axes.Both }); - } - - private void addGameplayComponents(Container target, WorkingBeatmap working, IBeatmap playableBeatmap) - { - var beatmapSkinProvider = new BeatmapSkinProvidingContainer(working.Skin); - - // the beatmapSkinProvider is used as the fallback source here to allow the ruleset-specific skin implementation - // full access to all skin sources. - var rulesetSkinProvider = new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(beatmapSkinProvider, playableBeatmap)); - - // load the skinning hierarchy first. - // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. - target.Add(new ScalingContainer(ScalingMode.Gameplay) - .WithChild(beatmapSkinProvider - .WithChild(target = rulesetSkinProvider))); - - target.AddRange(new Drawable[] + Children = new Drawable[] { - DrawableRuleset, + DrawableRuleset.With(r => + r.FrameStableComponents.Children = new Drawable[] + { + ScoreProcessor, + HealthProcessor, + breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor) + { + Breaks = working.Beatmap.Breaks + } + }), new ComboEffects(ScoreProcessor) - }); + } + }; - DrawableRuleset.FrameStableComponents.AddRange(new Drawable[] - { - ScoreProcessor, - HealthProcessor, - breakTracker = new BreakTracker(DrawableRuleset.GameplayStartTime, ScoreProcessor) - { - Breaks = working.Beatmap.Breaks - } - }); - } - - private void addOverlayComponents(Container target, WorkingBeatmap working) + private Drawable createOverlayComponents(WorkingBeatmap working) => new Container { - target.AddRange(new[] + RelativeSizeAxes = Axes.Both, + Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) @@ -342,8 +344,8 @@ namespace osu.Game.Screens.Play }, }, failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, }, - }); - } + } + }; private void onBreakTimeChanged(ValueChangedEvent isBreakTime) { diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f3ab8b86bc..07b759843c 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -22,7 +22,10 @@ namespace osu.Game.Skinning [Resolved] private ISampleStore samples { get; set; } - private bool requestedPlaying; + /// + /// Whether playback of this sound has been requested, regardless of whether it could be played or not (due to being paused, for instance). + /// + protected bool PlaybackRequested; public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -39,7 +42,7 @@ namespace osu.Game.Skinning protected virtual bool PlayWhenPaused => false; - private readonly AudioContainer samplesContainer; + protected readonly AudioContainer SamplesContainer; public SkinnableSound(ISampleInfo hitSamples) : this(new[] { hitSamples }) @@ -49,7 +52,7 @@ namespace osu.Game.Skinning public SkinnableSound(IEnumerable hitSamples) { this.hitSamples = hitSamples.ToArray(); - InternalChild = samplesContainer = new AudioContainer(); + InternalChild = SamplesContainer = new AudioContainer(); } private readonly IBindable samplePlaybackDisabled = new Bindable(); @@ -63,7 +66,7 @@ namespace osu.Game.Skinning samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); samplePlaybackDisabled.BindValueChanged(disabled => { - if (requestedPlaying) + if (PlaybackRequested) { if (disabled.NewValue && !PlayWhenPaused) stop(); @@ -87,13 +90,13 @@ namespace osu.Game.Skinning looping = value; - samplesContainer.ForEach(c => c.Looping = looping); + SamplesContainer.ForEach(c => c.Looping = looping); } } public void Play() { - requestedPlaying = true; + PlaybackRequested = true; play(); } @@ -102,7 +105,7 @@ namespace osu.Game.Skinning if (samplePlaybackDisabled.Value && !PlayWhenPaused) return; - samplesContainer.ForEach(c => + SamplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) c.Play(); @@ -111,13 +114,13 @@ namespace osu.Game.Skinning public void Stop() { - requestedPlaying = false; + PlaybackRequested = false; stop(); } private void stop() { - samplesContainer.ForEach(c => c.Stop()); + SamplesContainer.ForEach(c => c.Stop()); } protected override void SkinChanged(ISkinSource skin, bool allowFallback) @@ -146,7 +149,7 @@ namespace osu.Game.Skinning return ch; }).Where(c => c != null); - samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); + SamplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c)); // Start playback internally for the new samples if the previous ones were playing beforehand. if (wasPlaying) @@ -155,24 +158,24 @@ namespace osu.Game.Skinning #region Re-expose AudioContainer - public BindableNumber Volume => samplesContainer.Volume; + public BindableNumber Volume => SamplesContainer.Volume; - public BindableNumber Balance => samplesContainer.Balance; + public BindableNumber Balance => SamplesContainer.Balance; - public BindableNumber Frequency => samplesContainer.Frequency; + public BindableNumber Frequency => SamplesContainer.Frequency; - public BindableNumber Tempo => samplesContainer.Tempo; + public BindableNumber Tempo => SamplesContainer.Tempo; public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.AddAdjustment(type, adjustBindable); + => SamplesContainer.AddAdjustment(type, adjustBindable); public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) - => samplesContainer.RemoveAdjustment(type, adjustBindable); + => SamplesContainer.RemoveAdjustment(type, adjustBindable); public void RemoveAllAdjustments(AdjustableProperty type) - => samplesContainer.RemoveAllAdjustments(type); + => SamplesContainer.RemoveAllAdjustments(type); - public bool IsPlaying => samplesContainer.Any(s => s.Playing); + public bool IsPlaying => SamplesContainer.Any(s => s.Playing); #endregion } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 119c48836b..83e3b8203e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -4,15 +4,13 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Skinning; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardSample : Component + public class DrawableStoryboardSample : SkinnableSound { /// /// The amount of time allowable beyond the start time of the sample, for the sample to start. @@ -21,38 +19,37 @@ namespace osu.Game.Storyboards.Drawables private readonly StoryboardSampleInfo sampleInfo; - protected SampleChannel Channel { get; private set; } - public override bool RemoveWhenNotAlive => false; public DrawableStoryboardSample(StoryboardSampleInfo sampleInfo) + : base(sampleInfo) { this.sampleInfo = sampleInfo; LifetimeStart = sampleInfo.StartTime; } - [BackgroundDependencyLoader] - private void load(IBindable beatmap, IBindable> mods) - { - Channel = beatmap.Value.Skin.GetSample(sampleInfo); - if (Channel == null) - return; + [Resolved] + private IBindable> mods { get; set; } - Channel.Volume.Value = sampleInfo.Volume / 100.0; + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); foreach (var mod in mods.Value.OfType()) - mod.ApplyToSample(Channel); + { + foreach (var sample in SamplesContainer) + mod.ApplyToSample(sample); + } } protected override void Update() { base.Update(); - // TODO: this logic will need to be consolidated with other game samples like hit sounds. if (Time.Current < sampleInfo.StartTime) { // We've rewound before the start time of the sample - Channel?.Stop(); + Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) @@ -63,8 +60,8 @@ namespace osu.Game.Storyboards.Drawables { // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future - if (Time.Current - sampleInfo.StartTime < allowable_late_start) - Channel?.Play(); + if (!PlaybackRequested && Time.Current - sampleInfo.StartTime < allowable_late_start) + Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) @@ -72,13 +69,5 @@ namespace osu.Game.Storyboards.Drawables LifetimeEnd = sampleInfo.StartTime; } } - - protected override void Dispose(bool isDisposing) - { - Channel?.Stop(); - Channel = null; - - base.Dispose(isDisposing); - } } } From 56c8e4dacfba027675db746c668ac2541efddda0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 14:18:54 +0900 Subject: [PATCH 029/164] Fix failing tests --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 420bf29429..ac0e8eb0d4 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestQuickRetryFromFailedGameplay() { AddUntilStep("wait for fail", () => Player.HasFailed); - AddStep("quick retry", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + AddStep("quick retry", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); } @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestQuickExitFromFailedGameplay() { AddUntilStep("wait for fail", () => Player.HasFailed); - AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); } @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestQuickExitFromGameplay() { - AddStep("quick exit", () => Player.GameplayClockContainer.OfType().First().Action?.Invoke()); + AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType().First().Action?.Invoke()); confirmExited(); } From 1a70002cdd0bfe2c0fafd2d5f8a9e552ebf5c267 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:41:50 +0900 Subject: [PATCH 030/164] Split ignore into hit/miss --- osu.Game/Rulesets/Scoring/HitResult.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 1de62cf8e5..f5f2e269f0 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -13,12 +13,9 @@ namespace osu.Game.Rulesets.Scoring /// Indicates that the object has not been judged yet. /// [Description(@"")] - [Order(13)] + [Order(14)] None, - [Order(12)] - Ignore, - /// /// Indicates that the object has been judged as a miss. /// @@ -95,6 +92,12 @@ namespace osu.Game.Rulesets.Scoring [Description("L Bonus")] [Order(8)] LargeBonus, + + [Order(13)] + IgnoreMiss, + + [Order(12)] + IgnoreHit, } public static class HitResultExtensions @@ -151,7 +154,7 @@ namespace osu.Game.Rulesets.Scoring switch (result) { case HitResult.None: - case HitResult.Ignore: + case HitResult.IgnoreMiss: case HitResult.Miss: case HitResult.SmallTickMiss: case HitResult.LargeTickMiss: @@ -165,6 +168,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Whether a is scorable. /// - public static bool IsScorable(this HitResult result) => result > HitResult.Ignore; + public static bool IsScorable(this HitResult result) => result >= HitResult.Miss && result < HitResult.IgnoreMiss; } } From 5d1c3773790beb4b40ae02b11a230ddef47d7e05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 15:07:55 +0900 Subject: [PATCH 031/164] Fix HitObject samples getting stuck in a playing state on seeking far into the future --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 6 ++++++ osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 6 ++++++ osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 +++++++ osu.Game/Skinning/SkinnableSound.cs | 3 ++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 68f203db47..ba328e15c6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -110,6 +110,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public override void StopAllSamples() + { + base.StopAllSamples(); + slidingSample?.Stop(); + } + private void updateSlidingSample(ValueChangedEvent tracking) { if (tracking.NewValue) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b2a706833c..9e552981ea 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -124,6 +124,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public override void StopAllSamples() + { + base.StopAllSamples(); + spinningSample?.Stop(); + } + protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 796b8f7aae..56e3a98ca3 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -387,6 +387,11 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + /// + /// Stops playback of all samples. Automatically called when 's lifetime has been exceeded. + /// + public virtual void StopAllSamples() => Samples?.Stop(); + protected override void Update() { base.Update(); @@ -455,6 +460,8 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var nested in NestedHitObjects) nested.OnKilled(); + StopAllSamples(); + UpdateResult(false); } diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 07b759843c..c1f0b78d3b 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -73,7 +73,8 @@ namespace osu.Game.Skinning // it's not easy to know if a sample has finished playing (to end). // to keep things simple only resume playing looping samples. else if (Looping) - play(); + // schedule so we don't start playing a sample which is no longer alive. + Schedule(play); } }); } From 2f26728cdb6a28235a9e15c9e1acd0296938fb05 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 15:29:56 +0900 Subject: [PATCH 032/164] Add test coverage of editor sample playback --- .../Editing/TestSceneEditorSamplePlayback.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs new file mode 100644 index 0000000000..039a21fd94 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Audio; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorSamplePlayback : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestSlidingSampleStopsOnSeek() + { + DrawableSlider slider = null; + DrawableSample[] samples = null; + + AddStep("get first slider", () => + { + slider = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); + samples = slider.ChildrenOfType().ToArray(); + }); + + AddStep("start playback", () => EditorClock.Start()); + + AddUntilStep("wait for slider sliding then seek", () => + { + if (!slider.Tracking.Value) + return false; + + if (!samples.Any(s => s.Playing)) + return false; + + EditorClock.Seek(20000); + return true; + }); + + AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing)); + } + } +} From cee58e89a34e6e072a5d6adeaefafb90bec7aa0e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:32:02 +0900 Subject: [PATCH 033/164] Pad hit results --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index f5f2e269f0..6abf91e9d3 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"")] [Order(14)] - None, + None = 0, /// /// Indicates that the object has been judged as a miss. @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"Miss")] [Order(5)] - Miss, + Miss = 64, [Description(@"Meh")] [Order(4)] @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates small tick miss. /// [Order(11)] - SmallTickMiss, + SmallTickMiss = 128, /// /// Indicates a small tick hit. @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [Order(10)] - LargeTickMiss, + LargeTickMiss = 192, /// /// Indicates a large tick hit. @@ -84,17 +84,17 @@ namespace osu.Game.Rulesets.Scoring /// [Description("S Bonus")] [Order(9)] - SmallBonus, + SmallBonus = 254, /// /// Indicate a large bonus. /// [Description("L Bonus")] [Order(8)] - LargeBonus, + LargeBonus = 320, [Order(13)] - IgnoreMiss, + IgnoreMiss = 384, [Order(12)] IgnoreHit, From 07226c79b64918aeefcdb53df6dd339359700312 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:32:50 +0900 Subject: [PATCH 034/164] Add xmldocs --- osu.Game/Rulesets/Scoring/HitResult.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 6abf91e9d3..fc33ff9df2 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -87,15 +87,21 @@ namespace osu.Game.Rulesets.Scoring SmallBonus = 254, /// - /// Indicate a large bonus. + /// Indicates a large bonus. /// [Description("L Bonus")] [Order(8)] LargeBonus = 320, + /// + /// Indicates a miss that should be ignored for scoring purposes. + /// [Order(13)] IgnoreMiss = 384, + /// + /// Indicates a hit that should be ignored for scoring purposes. + /// [Order(12)] IgnoreHit, } From 519f376e7b5377b8965e05aa34815cee399428c4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:26:12 +0900 Subject: [PATCH 035/164] Standardise Judgement across all rulesets --- osu.Game/Rulesets/Judgements/Judgement.cs | 122 +++++++++++++++++++--- 1 file changed, 108 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 9105b920ca..ea7a8d8d25 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -11,31 +12,69 @@ namespace osu.Game.Rulesets.Judgements /// public class Judgement { + /// + /// The score awarded for a small bonus. + /// + public const double SMALL_BONUS_SCORE = 10; + + /// + /// The score awarded for a large bonus. + /// + public const double LARGE_BONUS_SCORE = 50; + /// /// The default health increase for a maximum judgement, as a proportion of total health. /// By default, each maximum judgement restores 5% of total health. /// protected const double DEFAULT_MAX_HEALTH_INCREASE = 0.05; + /// + /// Whether this should affect the current combo. + /// + [Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328 + public virtual bool AffectsCombo => true; + + /// + /// Whether this should be counted as base (combo) or bonus score. + /// + [Obsolete("Has no effect. Use HitResult members instead (e.g. use small-tick or bonus to not affect combo).")] // Can be removed 20210328 + public virtual bool IsBonus => !AffectsCombo; + /// /// The maximum that can be achieved. /// public virtual HitResult MaxResult => HitResult.Perfect; /// - /// Whether this should affect the current combo. + /// The minimum that can be achieved - the inverse of . /// - public virtual bool AffectsCombo => true; + public HitResult MinResult + { + get + { + switch (MaxResult) + { + case HitResult.SmallBonus: + case HitResult.LargeBonus: + case HitResult.IgnoreHit: + return HitResult.IgnoreMiss; - /// - /// Whether this should be counted as base (combo) or bonus score. - /// - public virtual bool IsBonus => !AffectsCombo; + case HitResult.SmallTickHit: + return HitResult.SmallTickMiss; + + case HitResult.LargeTickHit: + return HitResult.LargeTickMiss; + + default: + return HitResult.Miss; + } + } + } /// /// The numeric score representation for the maximum achievable result. /// - public int MaxNumericResult => NumericResultFor(MaxResult); + public double MaxNumericResult => ToNumericResult(MaxResult); /// /// The health increase for the maximum achievable result. @@ -43,18 +82,19 @@ namespace osu.Game.Rulesets.Judgements public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); /// - /// Retrieves the numeric score representation of a . + /// Retrieves the numeric score representation of a . /// - /// The to find the numeric score representation for. + /// The to find the numeric score representation for. /// The numeric score representation of . - protected virtual int NumericResultFor(HitResult result) => result > HitResult.Miss ? 1 : 0; + [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be removed 20210328 + protected virtual int NumericResultFor(HitResult result) => result == HitResult.Miss ? 0 : 1; /// /// Retrieves the numeric score representation of a . /// /// The to find the numeric score representation for. /// The numeric score representation of . - public int NumericResultFor(JudgementResult result) => NumericResultFor(result.Type); + public double NumericResultFor(JudgementResult result) => ToNumericResult(result.Type); /// /// Retrieves the numeric health increase of a . @@ -65,6 +105,21 @@ namespace osu.Game.Rulesets.Judgements { switch (result) { + default: + return 0; + + case HitResult.SmallTickHit: + return DEFAULT_MAX_HEALTH_INCREASE * 0.05; + + case HitResult.SmallTickMiss: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + + case HitResult.LargeTickHit: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.LargeTickMiss: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + case HitResult.Miss: return -DEFAULT_MAX_HEALTH_INCREASE; @@ -83,8 +138,11 @@ namespace osu.Game.Rulesets.Judgements case HitResult.Perfect: return DEFAULT_MAX_HEALTH_INCREASE * 1.05; - default: - return 0; + case HitResult.SmallBonus: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.LargeBonus: + return DEFAULT_MAX_HEALTH_INCREASE * 0.2; } } @@ -95,6 +153,42 @@ namespace osu.Game.Rulesets.Judgements /// The numeric health increase of . public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type); - public override string ToString() => $"AffectsCombo:{AffectsCombo} MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; + public override string ToString() => $"MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; + + public static double ToNumericResult(HitResult result) + { + switch (result) + { + default: + return 0; + + case HitResult.SmallTickHit: + return 1 / 30d; + + case HitResult.LargeTickHit: + return 1 / 10d; + + case HitResult.Meh: + return 1 / 6d; + + case HitResult.Ok: + return 1 / 3d; + + case HitResult.Good: + return 2 / 3d; + + case HitResult.Great: + return 1d; + + case HitResult.Perfect: + return 7 / 6d; + + case HitResult.SmallBonus: + return SMALL_BONUS_SCORE; + + case HitResult.LargeBonus: + return LARGE_BONUS_SCORE; + } + } } } From 4ca9a69de25dcf23cdfab050ed2632057ce50e19 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:26:36 +0900 Subject: [PATCH 036/164] Use new hit results in catch --- .../Judgements/CatchBananaJudgement.cs | 26 +------------------ .../Judgements/CatchDropletJudgement.cs | 12 +-------- .../Judgements/CatchJudgement.cs | 14 +--------- .../Judgements/CatchTinyDropletJudgement.cs | 26 +------------------ .../Drawables/DrawableCatchHitObject.cs | 3 +-- .../UI/CatchComboDisplay.cs | 4 +-- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 3 ++- 7 files changed, 9 insertions(+), 79 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index a7449ba4e1..b919102215 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -8,31 +8,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchBananaJudgement : CatchJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 1100; - } - } - - protected override double HealthIncreaseFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return DEFAULT_MAX_HEALTH_INCREASE * 0.75; - } - } + public override HitResult MaxResult => HitResult.LargeBonus; public override bool ShouldExplodeFor(JudgementResult result) => true; } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index e87ecba749..8fd7b93e4c 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -7,16 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchDropletJudgement : CatchJudgement { - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 30; - } - } + public override HitResult MaxResult => HitResult.LargeTickHit; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index 2149ed9712..ccafe0abc4 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -9,19 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { - public override HitResult MaxResult => HitResult.Perfect; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 300; - } - } + public override HitResult MaxResult => HitResult.Great; /// /// Whether fruit on the platter should explode or drop. diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs index d607b49ea4..d957d4171b 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -7,30 +7,6 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchTinyDropletJudgement : CatchJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 10; - } - } - - protected override double HealthIncreaseFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 0.02; - } - } + public override HitResult MaxResult => HitResult.SmallTickHit; } } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index 2fe017dc62..d03a764bda 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Catch.UI; using osuTK; using osuTK.Graphics; @@ -86,7 +85,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables if (CheckPosition == null) return; if (timeOffset >= 0 && Result != null) - ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss); + ApplyResult(r => r.Type = CheckPosition.Invoke(HitObject) ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index 58a3140bb5..cc01009dd9 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnNewResult(DrawableCatchHitObject judgedObject, JudgementResult result) { - if (!result.Judgement.AffectsCombo || !result.HasResult) + if (!result.Type.AffectsCombo() || !result.HasResult) return; if (result.Type == HitResult.Miss) @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result) { - if (!result.Judgement.AffectsCombo || !result.HasResult) + if (!result.Type.AffectsCombo() || !result.HasResult) return; updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value); diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index d3e63b0333..5e794a76aa 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osuTK; @@ -52,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.UI public void OnNewResult(DrawableCatchHitObject fruit, JudgementResult result) { - if (result.Judgement is IgnoreJudgement) + if (!result.Type.IsScorable()) return; void runAfterLoaded(Action action) From b1877b649ba53a00dc7c083c6d5799f6652e5d68 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:29:43 +0900 Subject: [PATCH 037/164] Use new hit results in mania --- .../Judgements/HoldNoteTickJudgement.cs | 14 +---------- .../Judgements/ManiaJudgement.cs | 24 ------------------- .../Objects/Drawables/DrawableHoldNote.cs | 2 +- .../Objects/Drawables/DrawableHoldNoteTick.cs | 5 ++-- .../Skinning/ManiaLegacySkinTransformer.cs | 3 +++ 5 files changed, 7 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 28e5d2cc1b..ee6cbbc828 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -7,18 +7,6 @@ namespace osu.Game.Rulesets.Mania.Judgements { public class HoldNoteTickJudgement : ManiaJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 20 : 0; - - protected override double HealthIncreaseFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Perfect: - return 0.01; - } - } + public override HitResult MaxResult => HitResult.LargeTickHit; } } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 53967ffa05..220dedc4a4 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -2,34 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { public class ManiaJudgement : Judgement { - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Meh: - return 50; - - case HitResult.Ok: - return 100; - - case HitResult.Good: - return 200; - - case HitResult.Great: - return 300; - - case HitResult.Perfect: - return 350; - } - } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 2ebcc5451a..ba6cad978d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -239,7 +239,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { if (Tail.AllJudged) { - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyResult(r => r.Type = r.Judgement.MaxResult); endHold(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 9b0322a6cd..98931dceed 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -73,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables var startTime = HoldStartTime?.Invoke(); if (startTime == null || startTime > HitObject.StartTime) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); else - ApplyResult(r => r.Type = HitResult.Perfect); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 439e6f7df2..3724269f4d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -130,6 +130,9 @@ namespace osu.Game.Rulesets.Mania.Skinning private Drawable getResult(HitResult result) { + if (!hitresult_mapping.ContainsKey(result)) + return null; + string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value ?? default_hitresult_skin_filenames[result]; From a77741927cf3e0b84e72cfde6210769047dd8d58 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 14:35:43 +0900 Subject: [PATCH 038/164] Use new hit results in osu --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 2 +- osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs | 6 +----- .../Objects/Drawables/DrawableSliderRepeat.cs | 3 +-- .../Objects/Drawables/DrawableSliderTail.cs | 3 +-- .../Objects/Drawables/DrawableSliderTick.cs | 3 +-- .../Objects/Drawables/DrawableSpinnerTick.cs | 4 +--- .../Objects/Drawables/Pieces/SpinnerBonusDisplay.cs | 3 ++- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 4 +--- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs | 6 +----- osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs | 8 +------- 12 files changed, 13 insertions(+), 33 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index f7909071ea..dbab048b25 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Tests { // multipled by 2 to nullify the score multiplier. (autoplay mod selected) var totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; - return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * SpinnerTick.SCORE_PER_TICK; + return totalScore == (int)(drawableSpinner.RotationTracker.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; }); addSeekStep(0); diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs index e528f65dca..1999785efe 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuIgnoreJudgement.cs @@ -7,10 +7,6 @@ namespace osu.Game.Rulesets.Osu.Judgements { public class OsuIgnoreJudgement : OsuJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 0; - - protected override double HealthIncreaseFor(HitResult result) => 0; + public override HitResult MaxResult => HitResult.IgnoreHit; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index d79ecb7b4e..f65077685f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (sliderRepeat.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 29a4929c1b..0939e2847a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -3,7 +3,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (!userTriggered && timeOffset >= 0) - ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss); + ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } private void updatePosition() => Position = HitObject.Position - slider.Position; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 66eb60aa28..9b68b446a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -10,7 +10,6 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (timeOffset >= 0) - ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : HitResult.Miss); + ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void UpdateInitialTransforms() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs index c390b673be..e9cede1398 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinnerTick.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Scoring; - namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSpinnerTick : DrawableOsuHitObject @@ -18,6 +16,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Apply a judgement result. /// /// Whether this tick was reached. - internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : HitResult.Miss); + internal void TriggerResult(bool hit) => ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs index b499d7a92b..1668cd73bd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; displayedCount = count; - bonusCounter.Text = $"{SpinnerBonusTick.SCORE_PER_TICK * count}"; + bonusCounter.Text = $"{Judgement.LARGE_BONUS_SCORE * count}"; bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index ac6c6905e4..b6c58a75d1 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderRepeatJudgement : OsuJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 30 : 0; + public override HitResult MaxResult => HitResult.LargeTickHit; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 1e54b576f1..aff3f38e17 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -29,9 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTailJudgement : OsuJudgement { - protected override int NumericResultFor(HitResult result) => 0; - - public override bool AffectsCombo => false; + public override HitResult MaxResult => HitResult.IgnoreHit; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 22f3f559db..a427ee1955 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTickJudgement : OsuJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? 10 : 0; + public override HitResult MaxResult => HitResult.LargeTickHit; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs index 0b1232b8db..235dc8710a 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerBonusTick.cs @@ -9,8 +9,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerBonusTick : SpinnerTick { - public new const int SCORE_PER_TICK = 50; - public SpinnerBonusTick() { Samples.Add(new HitSampleInfo { Name = "spinnerbonus" }); @@ -20,9 +18,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class OsuSpinnerBonusTickJudgement : OsuSpinnerTickJudgement { - protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; - - protected override double HealthIncreaseFor(HitResult result) => base.HealthIncreaseFor(result) * 2; + public override HitResult MaxResult => HitResult.LargeBonus; } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs index f54e7a9a15..d715b9a428 100644 --- a/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SpinnerTick.cs @@ -9,19 +9,13 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SpinnerTick : OsuHitObject { - public const int SCORE_PER_TICK = 10; - public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement(); protected override HitWindows CreateHitWindows() => HitWindows.Empty; public class OsuSpinnerTickJudgement : OsuJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => result == MaxResult ? SCORE_PER_TICK : 0; - - protected override double HealthIncreaseFor(HitResult result) => result == MaxResult ? 0.6 * base.HealthIncreaseFor(result) : 0; + public override HitResult MaxResult => HitResult.SmallBonus; } } } From c45b5690cfcc0469d01e27689f73e8c262a4a449 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:13:11 +0900 Subject: [PATCH 039/164] Use new hit results in taiko --- .../Judgements/TaikoDrumRollTickJudgement.cs | 14 +------------- .../Judgements/TaikoJudgement.cs | 15 --------------- .../Judgements/TaikoStrongJudgement.cs | 4 ++-- .../Objects/Drawables/DrawableDrumRoll.cs | 4 ++-- .../Objects/Drawables/DrawableDrumRollTick.cs | 7 +++---- .../Objects/Drawables/DrawableHit.cs | 6 +++--- .../Objects/Drawables/DrawableSwell.cs | 6 +++--- .../Objects/Drawables/DrawableSwellTick.cs | 5 ++--- .../Skinning/LegacyTaikoScroller.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs | 5 +++-- 10 files changed, 20 insertions(+), 48 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index a617028f1c..0551df3211 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -7,19 +7,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoDrumRollTickJudgement : TaikoJudgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - case HitResult.Great: - return 200; - - default: - return 0; - } - } + public override HitResult MaxResult => HitResult.SmallTickHit; protected override double HealthIncreaseFor(HitResult result) { diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index eb5f443365..3d22860814 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -10,21 +10,6 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public override HitResult MaxResult => HitResult.Great; - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - case HitResult.Good: - return 100; - - case HitResult.Great: - return 300; - - default: - return 0; - } - } - protected override double HealthIncreaseFor(HitResult result) { switch (result) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs index e045ea324f..06495ad9f4 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs @@ -7,9 +7,9 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoStrongJudgement : TaikoJudgement { + public override HitResult MaxResult => HitResult.SmallBonus; + // MainObject already changes the HP protected override double HealthIncreaseFor(HitResult result) => 0; - - public override bool AffectsCombo => false; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 2c1c2d2bc1..286feac5ba 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); + ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else ApplyResult(r => r.Type = HitResult.Miss); @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!MainObject.Judged) return; - ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index 62405cf047..9d7dcc7218 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -4,7 +4,6 @@ using System; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -34,14 +33,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (timeOffset > HitObject.HitWindow) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset) > HitObject.HitWindow) return; - ApplyResult(r => r.Type = HitResult.Great); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } protected override void UpdateStateTransforms(ArmedState state) @@ -74,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!MainObject.Judged) return; - ApplyResult(r => r.Type = MainObject.IsHit ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = MainObject.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); } public override bool OnPressed(TaikoAction action) => false; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 3a6eaa83db..03df28f850 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -257,19 +257,19 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!MainObject.Result.IsHit) { - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } if (!userTriggered) { if (timeOffset - MainObject.Result.TimeOffset > second_hit_window) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window) - ApplyResult(r => r.Type = MainObject.Result.Type); + ApplyResult(r => r.Type = r.Judgement.MaxResult); } public override bool OnPressed(TaikoAction action) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 7294587b10..11ff0729e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } } - nextTick?.TriggerResult(HitResult.Great); + nextTick?.TriggerResult(true); var numHits = ticks.Count(r => r.IsHit); @@ -208,10 +208,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables continue; } - tick.TriggerResult(HitResult.Miss); + tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Good : HitResult.Miss; + var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; ApplyResult(r => r.Type = hitResult); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 1685576f0d..6202583494 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces; using osu.Game.Skinning; @@ -19,10 +18,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected override void UpdateInitialTransforms() => this.FadeOut(); - public void TriggerResult(HitResult type) + public void TriggerResult(bool hit) { HitObject.StartTime = Time.Current; - ApplyResult(r => r.Type = type); + ApplyResult(r => r.Type = hit ? r.Judgement.MaxResult : r.Judgement.MinResult); } protected override void CheckForResult(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs index 03813e0a99..928072c491 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning var r = result.NewValue; // always ignore hitobjects that don't affect combo (drumroll ticks etc.) - if (r?.Judgement.AffectsCombo == false) + if (r?.Type.AffectsCombo() == false) return; passing = r == null || r.Type > HitResult.Miss; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs index b937beae3c..6a16f311bf 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Screens.Play; @@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.UI lastObjectHit = true; } - if (!result.Judgement.AffectsCombo) + if (!result.Type.AffectsCombo()) return; lastObjectHit = result.IsHit; @@ -115,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.UI } private bool triggerComboClear(JudgementResult judgementResult) - => (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Judgement.AffectsCombo && judgementResult.IsHit; + => (judgementResult.ComboAtJudgement + 1) % 50 == 0 && judgementResult.Type.AffectsCombo() && judgementResult.IsHit; private bool triggerSwellClear(JudgementResult judgementResult) => judgementResult.Judgement is TaikoSwellJudgement && judgementResult.IsHit; From 6264a01eccff56acb06f74b04d2a40534bf263d8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:14:03 +0900 Subject: [PATCH 040/164] Add guard against using the wrong hit result --- .../Objects/Drawables/DrawableHitObject.cs | 15 ++++++++++++++ osu.Game/Rulesets/Scoring/HitResult.cs | 20 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7c05bc9aa7..2a3e3716e5 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -475,6 +475,21 @@ namespace osu.Game.Rulesets.Objects.Drawables if (!Result.HasResult) throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); + // Some (especially older) rulesets use scorable judgements instead of the newer ignorehit/ignoremiss judgements. + if (Result.Judgement.MaxResult == HitResult.IgnoreHit) + { + if (Result.Type == HitResult.Miss) + Result.Type = HitResult.IgnoreMiss; + else if (Result.Type >= HitResult.Meh && Result.Type <= HitResult.Perfect) + Result.Type = HitResult.IgnoreHit; + } + + if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult)) + { + throw new InvalidOperationException( + $"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}])."); + } + // Ensure that the judgement is given a valid time offset, because this may not get set by the caller var endTime = HitObject.GetEndTime(); diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index fc33ff9df2..7a02db190a 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using System.Diagnostics; using osu.Game.Utils; namespace osu.Game.Rulesets.Scoring @@ -175,5 +176,24 @@ namespace osu.Game.Rulesets.Scoring /// Whether a is scorable. /// public static bool IsScorable(this HitResult result) => result >= HitResult.Miss && result < HitResult.IgnoreMiss; + + /// + /// Whether a is valid within a given range. + /// + /// The to check. + /// The minimum . + /// The maximum . + /// Whether falls between and . + public static bool IsValidHitResult(this HitResult result, HitResult minResult, HitResult maxResult) + { + if (result == HitResult.None) + return false; + + if (result == minResult || result == maxResult) + return true; + + Debug.Assert(minResult <= maxResult); + return result > minResult && result < maxResult; + } } } From a1394c183056c52a4d1648d6120a2a65e85fa21b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:20:47 +0900 Subject: [PATCH 041/164] Fix a few missed judgements --- .../Judgements/OsuJudgement.cs | 18 ------------------ .../Rulesets/Judgements/IgnoreJudgement.cs | 6 +----- .../Rulesets/Judgements/JudgementResult.cs | 2 +- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index bf30fbc351..1a88e2a8b2 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -9,23 +9,5 @@ namespace osu.Game.Rulesets.Osu.Judgements public class OsuJudgement : Judgement { public override HitResult MaxResult => HitResult.Great; - - protected override int NumericResultFor(HitResult result) - { - switch (result) - { - default: - return 0; - - case HitResult.Meh: - return 50; - - case HitResult.Good: - return 100; - - case HitResult.Great: - return 300; - } - } } } diff --git a/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs b/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs index 1871249c94..d2a434058d 100644 --- a/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs +++ b/osu.Game/Rulesets/Judgements/IgnoreJudgement.cs @@ -7,10 +7,6 @@ namespace osu.Game.Rulesets.Judgements { public class IgnoreJudgement : Judgement { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 0; - - protected override double HealthIncreaseFor(HitResult result) => 0; + public override HitResult MaxResult => HitResult.IgnoreHit; } } diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 59a7917e55..3a35fd4433 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Judgements /// /// Whether a successful hit occurred. /// - public bool IsHit => Type > HitResult.Miss; + public bool IsHit => Type.IsHit(); /// /// Creates a new . From 31fae045fa26854c3d4af4c988b03ea792aa9137 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:25:31 +0900 Subject: [PATCH 042/164] Update judgement processors with new hit results --- .../Scoring/CatchScoreProcessor.cs | 1 - .../Scoring/ManiaScoreProcessor.cs | 2 - .../Scoring/OsuScoreProcessor.cs | 2 - .../Scoring/TaikoScoreProcessor.cs | 2 - .../TestSceneDrainingHealthProcessor.cs | 16 ++--- .../Gameplay/TestSceneScoreProcessor.cs | 32 +++------- .../Scoring/DrainingHealthProcessor.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 61 ++++++++----------- osu.Game/Scoring/ScoreManager.cs | 2 +- 9 files changed, 47 insertions(+), 73 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 4c7bc4ab73..2cc05826b4 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public override HitWindows CreateHitWindows() => new CatchHitWindows(); } } diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 4b2f643333..71cc0bdf1f 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -10,7 +10,5 @@ namespace osu.Game.Rulesets.Mania.Scoring protected override double DefaultAccuracyPortion => 0.95; protected override double DefaultComboPortion => 0.05; - - public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 86ec76e373..44118227d9 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -25,7 +25,5 @@ namespace osu.Game.Rulesets.Osu.Scoring return new OsuJudgementResult(hitObject, judgement); } } - - public override HitWindows CreateHitWindows() => new OsuHitWindows(); } } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index e29ea87d25..1829ea2513 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -10,7 +10,5 @@ namespace osu.Game.Rulesets.Taiko.Scoring protected override double DefaultAccuracyPortion => 0.75; protected override double DefaultComboPortion => 0.25; - - public override HitWindows CreateHitWindows() => new TaikoHitWindows(); } } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 460ad1b898..0bb2c4b60c 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Gameplay beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 0 }); for (double time = 0; time < 5000; time += 100) - beatmap.HitObjects.Add(new JudgeableHitObject(false) { StartTime = time }); + beatmap.HitObjects.Add(new JudgeableHitObject(HitResult.LargeBonus) { StartTime = time }); beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 5000 }); createProcessor(beatmap); @@ -215,23 +215,23 @@ namespace osu.Game.Tests.Gameplay private class JudgeableHitObject : HitObject { - private readonly bool affectsCombo; + private readonly HitResult maxResult; - public JudgeableHitObject(bool affectsCombo = true) + public JudgeableHitObject(HitResult maxResult = HitResult.Perfect) { - this.affectsCombo = affectsCombo; + this.maxResult = maxResult; } - public override Judgement CreateJudgement() => new TestJudgement(affectsCombo); + public override Judgement CreateJudgement() => new TestJudgement(maxResult); protected override HitWindows CreateHitWindows() => new HitWindows(); private class TestJudgement : Judgement { - public override bool AffectsCombo { get; } + public override HitResult MaxResult { get; } - public TestJudgement(bool affectsCombo) + public TestJudgement(HitResult maxResult) { - AffectsCombo = affectsCombo; + MaxResult = maxResult; } } } diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index c9ab4fa489..432e3df95e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -17,13 +17,13 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestNoScoreIncreaseFromMiss() { - var beatmap = new Beatmap { HitObjects = { new TestHitObject() } }; + var beatmap = new Beatmap { HitObjects = { new HitObject() } }; var scoreProcessor = new ScoreProcessor(); scoreProcessor.ApplyBeatmap(beatmap); // Apply a miss judgement - scoreProcessor.ApplyResult(new JudgementResult(new TestHitObject(), new TestJudgement()) { Type = HitResult.Miss }); + scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement()) { Type = HitResult.Miss }); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); } @@ -31,37 +31,25 @@ namespace osu.Game.Tests.Gameplay [Test] public void TestOnlyBonusScore() { - var beatmap = new Beatmap { HitObjects = { new TestBonusHitObject() } }; + var beatmap = new Beatmap { HitObjects = { new HitObject() } }; var scoreProcessor = new ScoreProcessor(); scoreProcessor.ApplyBeatmap(beatmap); // Apply a judgement - scoreProcessor.ApplyResult(new JudgementResult(new TestBonusHitObject(), new TestBonusJudgement()) { Type = HitResult.Perfect }); + scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement(HitResult.LargeBonus)) { Type = HitResult.LargeBonus }); - Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(100)); - } - - private class TestHitObject : HitObject - { - public override Judgement CreateJudgement() => new TestJudgement(); + Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(Judgement.LARGE_BONUS_SCORE)); } private class TestJudgement : Judgement { - protected override int NumericResultFor(HitResult result) => 100; - } + public override HitResult MaxResult { get; } - private class TestBonusHitObject : HitObject - { - public override Judgement CreateJudgement() => new TestBonusJudgement(); - } - - private class TestBonusJudgement : Judgement - { - public override bool AffectsCombo => false; - - protected override int NumericResultFor(HitResult result) => 100; + public TestJudgement(HitResult maxResult = HitResult.Perfect) + { + MaxResult = maxResult; + } } } } diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 130907b242..91793e3f70 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Scoring { base.ApplyResultInternal(result); - if (!result.Judgement.IsBonus) + if (!result.Type.IsBonus()) healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result))); } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 6fa5a87c8e..7a5b707357 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -71,7 +71,6 @@ namespace osu.Game.Rulesets.Scoring private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; - private double bonusScore; private readonly List hitEvents = new List(); private HitObject lastHitObject; @@ -116,14 +115,15 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; - if (result.Judgement.AffectsCombo) + if (!result.Type.IsScorable()) + return; + + if (result.Type.AffectsCombo()) { switch (result.Type) { - case HitResult.None: - break; - case HitResult.Miss: + case HitResult.LargeTickMiss: Combo.Value = 0; break; @@ -133,22 +133,16 @@ namespace osu.Game.Rulesets.Scoring } } - double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; - if (result.Judgement.IsBonus) + if (!result.Type.IsBonus()) { - if (result.IsHit) - bonusScore += scoreIncrease; - } - else - { - if (result.HasResult) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; - baseScore += scoreIncrease; rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; + hitEvents.Add(CreateHitEvent(result)); lastHitObject = result.HitObject; @@ -171,22 +165,19 @@ namespace osu.Game.Rulesets.Scoring if (result.FailedAtJudgement) return; - double scoreIncrease = result.Type == HitResult.Miss ? 0 : result.Judgement.NumericResultFor(result); + if (!result.Type.IsScorable()) + return; - if (result.Judgement.IsBonus) - { - if (result.IsHit) - bonusScore -= scoreIncrease; - } - else - { - if (result.HasResult) - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + double scoreIncrease = result.Type.IsHit() ? result.Judgement.NumericResultFor(result) : 0; + if (!result.Type.IsBonus()) + { baseScore -= scoreIncrease; rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + Debug.Assert(hitEvents.Count > 0); lastHitObject = hitEvents[^1].LastHitObject; hitEvents.RemoveAt(hitEvents.Count - 1); @@ -207,7 +198,7 @@ namespace osu.Game.Rulesets.Scoring return GetScore(mode, maxHighestCombo, maxBaseScore > 0 ? baseScore / maxBaseScore : 0, maxHighestCombo > 0 ? (double)HighestCombo.Value / maxHighestCombo : 0, - bonusScore); + scoreResultCounts); } /// @@ -217,9 +208,9 @@ namespace osu.Game.Rulesets.Scoring /// The maximum combo achievable in the beatmap. /// The accuracy percentage achieved by the player. /// The proportion of achieved by the player. - /// Any bonus score to be added. + /// Any statistics to be factored in. /// The total score. - public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, double bonusScore) + public double GetScore(ScoringMode mode, int maxCombo, double accuracyRatio, double comboRatio, Dictionary statistics) { switch (mode) { @@ -228,14 +219,18 @@ namespace osu.Game.Rulesets.Scoring double accuracyScore = accuracyPortion * accuracyRatio; double comboScore = comboPortion * comboRatio; - return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; + return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier; case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - return bonusScore + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); + return getBonusScore(statistics) + (accuracyRatio * maxCombo * 300) * (1 + Math.Max(0, (comboRatio * maxCombo) - 1) * scoreMultiplier / 25); } } + private double getBonusScore(Dictionary statistics) + => statistics.GetOrDefault(HitResult.SmallBonus) * Judgement.SMALL_BONUS_SCORE + + statistics.GetOrDefault(HitResult.LargeBonus) * Judgement.LARGE_BONUS_SCORE; + private ScoreRank rankFrom(double acc) { if (acc == 1) @@ -282,7 +277,6 @@ namespace osu.Game.Rulesets.Scoring baseScore = 0; rollingMaxBaseScore = 0; - bonusScore = 0; TotalScore.Value = 0; Accuracy.Value = 1; @@ -309,9 +303,7 @@ namespace osu.Game.Rulesets.Scoring score.Rank = Rank.Value; score.Date = DateTimeOffset.Now; - var hitWindows = CreateHitWindows(); - - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.IsScorable())) score.Statistics[result] = GetStatistic(result); score.HitEvents = hitEvents; @@ -320,6 +312,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Create a for this processor. /// + [Obsolete("Method is now unused.")] // Can be removed 20210328 public virtual HitWindows CreateHitWindows() => new HitWindows(); } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 619ca76598..561ca631b3 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -181,7 +181,7 @@ namespace osu.Game.Scoring scoreProcessor.Mods.Value = score.Mods; - Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, 0)); + Value = (long)Math.Round(scoreProcessor.GetScore(ScoringMode.Value, beatmapMaxCombo, score.Accuracy, (double)score.MaxCombo / beatmapMaxCombo, score.Statistics)); } } From bc8f6a58fd307302cb998e1e7af9fc79dd443740 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:26:42 +0900 Subject: [PATCH 043/164] Update PF/SD with new hit results --- osu.Game/Rulesets/Mods/ModPerfect.cs | 3 +-- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 4 +++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 65f1a972ed..df0fc9c4b6 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -16,8 +16,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "SS or quit."; protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => !(result.Judgement is IgnoreJudgement) - && result.Judgement.AffectsCombo + => result.Type.AffectsAccuracy() && result.Type != result.Judgement.MaxResult; } } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index df10262845..ae71041a64 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Mods healthProcessor.FailConditions += FailCondition; } - protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) => !result.IsHit && result.Judgement.AffectsCombo; + protected virtual bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + => result.Type.AffectsCombo() + && !result.IsHit; } } From 4ef7ab28728e9c4543b5a049b153f742a925378b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 15:36:08 +0900 Subject: [PATCH 044/164] Fix tests --- .../Mods/TestSceneCatchModPerfect.cs | 2 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 4 ++-- osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs | 2 +- osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index c1b7214d72..3e06e78dba 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods public void TestDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Droplet { StartTime = 1000 }), shouldMiss); // We only care about testing misses, hits are tested via JuiceStream - [TestCase(false)] + [TestCase(true)] public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index 36289bda93..ae42bf8a90 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); - assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Idle); + assertStateAfterResult(new JudgementResult(new StrongHitObject(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Idle); } [Test] @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Kiai); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Kiai); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); } diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index efeb5eeba2..7264083338 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -263,7 +263,7 @@ namespace osu.Game.Tests.Gameplay public double Duration { get; set; } = 5000; public JudgeableLongHitObject() - : base(false) + : base(HitResult.LargeBonus) { } diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 64d1024efb..84b7bc3723 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -35,10 +35,10 @@ namespace osu.Game.Tests.Rulesets.Scoring } [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, 800_000)] + [TestCase(ScoringMode.Standardised, HitResult.Good, 900_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] [TestCase(ScoringMode.Classic, HitResult.Meh, 50)] - [TestCase(ScoringMode.Classic, HitResult.Good, 100)] + [TestCase(ScoringMode.Classic, HitResult.Good, 200)] [TestCase(ScoringMode.Classic, HitResult.Great, 300)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { From e789e06c86ac973754a4f71b053b7b1becdb97f7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:00:45 +0900 Subject: [PATCH 045/164] Don't display hold note tick judgements --- .../Objects/Drawables/DrawableHoldNoteTick.cs | 2 ++ osu.Game.Rulesets.Mania/UI/Column.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 98931dceed..f265419aa0 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableHoldNoteTick : DrawableManiaHitObject { + public override bool DisplayResult => false; + /// /// References the time at which the user started holding the hold note. /// diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 9aabcc6699..c28a1c13d8 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI if (result.IsHit) hitPolicy.HandleHit(judgedObject); - if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) + if (!result.IsHit || !DisplayJudgements.Value) return; HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result))); From 903bcd747e777757150310877bf256594b818bcf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 16:39:29 +0900 Subject: [PATCH 046/164] Revert unintended changes --- osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index ccafe0abc4..fd61647a7c 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { - public override HitResult MaxResult => HitResult.Great; + public override HitResult MaxResult => HitResult.Perfect; /// /// Whether fruit on the platter should explode or drop. diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 286feac5ba..dfab24f239 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); + ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); } else ApplyResult(r => r.Type = HitResult.Miss); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 11ff0729e2..999a159cce 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; + var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Good : HitResult.Miss; ApplyResult(r => r.Type = hitResult); } From f439c1afbc7310991681ba2bd1867a7540494508 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:16:55 +0900 Subject: [PATCH 047/164] Make osu/taiko/catch use Ok+Great --- .../TestSceneComboCounter.cs | 2 +- .../Difficulty/CatchPerformanceCalculator.cs | 2 +- .../Judgements/CatchJudgement.cs | 2 +- .../Scoring/CatchHitWindows.cs | 2 +- .../Difficulty/OsuPerformanceCalculator.cs | 10 ++--- .../Objects/Drawables/DrawableSpinner.cs | 2 +- .../Replays/OsuAutoGenerator.cs | 6 +-- .../Scoring/OsuHitWindows.cs | 4 +- .../Skinning/TestSceneDrawableTaikoMascot.cs | 6 +-- .../Skinning/TestSceneHitExplosion.cs | 4 +- .../Skinning/TestSceneTaikoScroller.cs | 2 +- .../TestSceneFlyingHits.cs | 2 +- .../TestSceneHits.cs | 8 ++-- .../Difficulty/TaikoPerformanceCalculator.cs | 6 +-- .../Judgements/TaikoJudgement.cs | 2 +- .../Objects/Drawables/DrawableDrumRoll.cs | 2 +- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Scoring/TaikoHitWindows.cs | 4 +- .../Skinning/TaikoLegacySkinTransformer.cs | 8 ++-- .../TaikoSkinComponents.cs | 4 +- .../UI/DrawableTaikoJudgement.cs | 2 +- osu.Game.Rulesets.Taiko/UI/HitExplosion.cs | 6 +-- .../Rulesets/Scoring/ScoreProcessorTest.cs | 4 +- .../Visual/Gameplay/TestSceneHitErrorMeter.cs | 2 +- .../Scoring/Legacy/ScoreInfoExtensions.cs | 37 +------------------ osu.Game/Skinning/LegacySkin.cs | 2 +- osu.Game/Tests/TestScoreInfo.cs | 4 +- 27 files changed, 53 insertions(+), 84 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs index e79792e04a..c7b322c8a0 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests [Test] public void TestCatchComboCounter() { - AddRepeatStep("perform hit", () => performJudgement(HitResult.Perfect), 20); + AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 20); AddStep("perform miss", () => performJudgement(HitResult.Miss)); AddStep("randomize judged object colour", () => diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index d700f79e5b..a4b9ca35eb 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty { mods = Score.Mods; - fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect); + fruitsHit = Score.Statistics.GetOrDefault(HitResult.Great); ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit); tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit); tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss); diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index fd61647a7c..ccafe0abc4 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Judgements { public class CatchJudgement : Judgement { - public override HitResult MaxResult => HitResult.Perfect; + public override HitResult MaxResult => HitResult.Great; /// /// Whether fruit on the platter should explode or drop. diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs index ff793a372e..0a444d923e 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchHitWindows.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Scoring { switch (result) { - case HitResult.Perfect: + case HitResult.Great: case HitResult.Miss: return true; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 6f4c0f9cfa..02577461f0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double accuracy; private int scoreMaxCombo; private int countGreat; - private int countGood; + private int countOk; private int countMeh; private int countMiss; @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty accuracy = Score.Accuracy; scoreMaxCombo = Score.MaxCombo; countGreat = Score.Statistics.GetOrDefault(HitResult.Great); - countGood = Score.Statistics.GetOrDefault(HitResult.Good); + countOk = Score.Statistics.GetOrDefault(HitResult.Ok); countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty int amountHitObjectsWithAccuracy = countHitCircles; if (amountHitObjectsWithAccuracy > 0) - betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); + betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countOk * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6); else betterAccuracyPercentage = 0; @@ -204,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty return accuracyValue; } - private int totalHits => countGreat + countGood + countMeh + countMiss; - private int totalSuccessfulHits => countGreat + countGood + countMeh; + private int totalHits => countGreat + countOk + countMeh + countMiss; + private int totalSuccessfulHits => countGreat + countOk + countMeh; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index a57bb466c7..d77213f3ed 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (Progress >= 1) r.Type = HitResult.Great; else if (Progress > .9) - r.Type = HitResult.Good; + r.Type = HitResult.Ok; else if (Progress > .75) r.Type = HitResult.Meh; else if (Time.Current >= Spinner.EndTime) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index 9b350278f3..954a217473 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -137,13 +137,13 @@ namespace osu.Game.Rulesets.Osu.Replays if (!(h is Spinner)) AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Meh), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } - else if (h.StartTime - hitWindows.WindowFor(HitResult.Good) > endTime + hitWindows.WindowFor(HitResult.Good) + 50) + else if (h.StartTime - hitWindows.WindowFor(HitResult.Ok) > endTime + hitWindows.WindowFor(HitResult.Ok) + 50) { if (!(prev is Spinner) && h.StartTime - endTime < 1000) - AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Good), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(endTime + hitWindows.WindowFor(HitResult.Ok), new Vector2(prev.StackedEndPosition.X, prev.StackedEndPosition.Y))); if (!(h is Spinner)) - AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Good), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); + AddFrameToReplay(new OsuReplayFrame(h.StartTime - hitWindows.WindowFor(HitResult.Ok), new Vector2(h.StackedPosition.X, h.StackedPosition.Y))); } } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs index 6f2998006f..dafe63a6d1 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHitWindows.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Scoring private static readonly DifficultyRange[] osu_ranges = { new DifficultyRange(HitResult.Great, 80, 50, 20), - new DifficultyRange(HitResult.Good, 140, 100, 60), + new DifficultyRange(HitResult.Ok, 140, 100, 60), new DifficultyRange(HitResult.Meh, 200, 150, 100), new DifficultyRange(HitResult.Miss, 400, 400, 400), }; @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Scoring switch (result) { case HitResult.Great: - case HitResult.Good: + case HitResult.Ok: case HitResult.Meh: case HitResult.Miss: return true; diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs index ae42bf8a90..99e103da3b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableTaikoMascot.cs @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning createDrawableRuleset(); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Kiai); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoStrongJudgement()) { Type = HitResult.IgnoreMiss }, TaikoMascotAnimationState.Kiai); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); } @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Miss }, TaikoMascotAnimationState.Fail); assertStateAfterResult(new JudgementResult(new DrumRoll(), new TaikoDrumRollJudgement()) { Type = HitResult.Great }, TaikoMascotAnimationState.Idle); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Idle); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Idle); } [TestCase(true)] @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddRepeatStep("reach 49 combo", () => applyNewResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Great }), 49); - assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Good }, TaikoMascotAnimationState.Clear); + assertStateAfterResult(new JudgementResult(new Hit(), new TaikoJudgement()) { Type = HitResult.Ok }, TaikoMascotAnimationState.Clear); } [TestCase(true, TaikoMascotAnimationState.Kiai)] diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs index 45c94a8a86..fecb5d4a74 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneHitExplosion.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning public void TestNormalHit() { AddStep("Great", () => SetContents(() => getContentFor(createHit(HitResult.Great)))); - AddStep("Good", () => SetContents(() => getContentFor(createHit(HitResult.Good)))); + AddStep("Ok", () => SetContents(() => getContentFor(createHit(HitResult.Ok)))); AddStep("Miss", () => SetContents(() => getContentFor(createHit(HitResult.Miss)))); } @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning public void TestStrongHit([Values(false, true)] bool hitBoth) { AddStep("Great", () => SetContents(() => getContentFor(createStrongHit(HitResult.Great, hitBoth)))); - AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Good, hitBoth)))); + AddStep("Good", () => SetContents(() => getContentFor(createStrongHit(HitResult.Ok, hitBoth)))); } private Drawable getContentFor(DrawableTestHit hit) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs index 16ef5b968d..114038b81c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoScroller.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); AddToggleStep("Toggle passing", passing => this.ChildrenOfType().ForEach(s => s.LastResult.Value = - new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Perfect : HitResult.Miss })); + new JudgementResult(null, new Judgement()) { Type = passing ? HitResult.Great : HitResult.Miss })); AddToggleStep("toggle playback direction", reversed => this.reversed = reversed); } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs index 7492a76a67..63854e7ead 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests DrawableDrumRollTick h; DrawableRuleset.Playfield.Add(h = new DrawableDrumRollTick(tick) { JudgementType = hitType }); - ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Perfect }); + ((TaikoPlayfield)DrawableRuleset.Playfield).OnNewResult(h, new JudgementResult(tick, new TaikoDrumRollTickJudgement()) { Type = HitResult.Great }); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index b6cfe368f7..0f605be8f9 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addHitJudgement(bool kiai) { - HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; + HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great; var cpi = new ControlPointInfo(); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); - var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; + var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) }; DrawableRuleset.Playfield.Add(h); @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Tests private void addStrongHitJudgement(bool kiai) { - HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; + HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Ok : HitResult.Great; var cpi = new ControlPointInfo(); cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); - var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; + var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Ok ? -0.1f : -0.05f, hitResult == HitResult.Ok ? 0.1f : 0.05f) }; DrawableRuleset.Playfield.Add(h); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index b9d95a6ba6..c04fffa2e7 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private Mod[] mods; private int countGreat; - private int countGood; + private int countOk; private int countMeh; private int countMiss; @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { mods = Score.Mods; countGreat = Score.Statistics.GetOrDefault(HitResult.Great); - countGood = Score.Statistics.GetOrDefault(HitResult.Good); + countOk = Score.Statistics.GetOrDefault(HitResult.Ok); countMeh = Score.Statistics.GetOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetOrDefault(HitResult.Miss); @@ -102,6 +102,6 @@ namespace osu.Game.Rulesets.Taiko.Difficulty return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); } - private int totalHits => countGreat + countGood + countMeh + countMiss; + private int totalHits => countGreat + countOk + countMeh + countMiss; } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 3d22860814..e272c1a4ef 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements case HitResult.Miss: return -1.0; - case HitResult.Good: + case HitResult.Ok: return 1.1; case HitResult.Great: diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index dfab24f239..286feac5ba 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (countHit >= HitObject.RequiredGoodHits) { - ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Good); + ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else ApplyResult(r => r.Type = HitResult.Miss); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 999a159cce..11ff0729e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Good : HitResult.Miss; + var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; ApplyResult(r => r.Type = hitResult); } diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs index 9d273392ff..cf806c0c97 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHitWindows.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring private static readonly DifficultyRange[] taiko_ranges = { new DifficultyRange(HitResult.Great, 50, 35, 20), - new DifficultyRange(HitResult.Good, 120, 80, 50), + new DifficultyRange(HitResult.Ok, 120, 80, 50), new DifficultyRange(HitResult.Miss, 135, 95, 70), }; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring switch (result) { case HitResult.Great: - case HitResult.Good: + case HitResult.Ok: case HitResult.Miss: return true; } diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index c222ccb51f..73a56f3fbc 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -74,8 +74,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; - case TaikoSkinComponents.TaikoExplosionGood: - case TaikoSkinComponents.TaikoExplosionGoodStrong: + case TaikoSkinComponents.TaikoExplosionOk: + case TaikoSkinComponents.TaikoExplosionOkStrong: case TaikoSkinComponents.TaikoExplosionGreat: case TaikoSkinComponents.TaikoExplosionGreatStrong: case TaikoSkinComponents.TaikoExplosionMiss: @@ -106,10 +106,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning case TaikoSkinComponents.TaikoExplosionMiss: return "taiko-hit0"; - case TaikoSkinComponents.TaikoExplosionGood: + case TaikoSkinComponents.TaikoExplosionOk: return "taiko-hit100"; - case TaikoSkinComponents.TaikoExplosionGoodStrong: + case TaikoSkinComponents.TaikoExplosionOkStrong: return "taiko-hit100k"; case TaikoSkinComponents.TaikoExplosionGreat: diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index 0d785adb4a..b274608d84 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -16,8 +16,8 @@ namespace osu.Game.Rulesets.Taiko PlayfieldBackgroundRight, BarLine, TaikoExplosionMiss, - TaikoExplosionGood, - TaikoExplosionGoodStrong, + TaikoExplosionOk, + TaikoExplosionOkStrong, TaikoExplosionGreat, TaikoExplosionGreatStrong, Scroller, diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index f91bbb14e8..cbfc5a8628 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.UI { switch (Result.Type) { - case HitResult.Good: + case HitResult.Ok: JudgementBody.Colour = colours.GreenLight; break; diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index efd1b25046..6d800b812d 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -60,10 +60,10 @@ namespace osu.Game.Rulesets.Taiko.UI case HitResult.Miss: return TaikoSkinComponents.TaikoExplosionMiss; - case HitResult.Good: + case HitResult.Ok: return useStrongExplosion(judgedObject) - ? TaikoSkinComponents.TaikoExplosionGoodStrong - : TaikoSkinComponents.TaikoExplosionGood; + ? TaikoSkinComponents.TaikoExplosionOkStrong + : TaikoSkinComponents.TaikoExplosionOk; case HitResult.Great: return useStrongExplosion(judgedObject) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 84b7bc3723..ace57aad1d 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -35,10 +35,10 @@ namespace osu.Game.Tests.Rulesets.Scoring } [TestCase(ScoringMode.Standardised, HitResult.Meh, 750_000)] - [TestCase(ScoringMode.Standardised, HitResult.Good, 900_000)] + [TestCase(ScoringMode.Standardised, HitResult.Ok, 800_000)] [TestCase(ScoringMode.Standardised, HitResult.Great, 1_000_000)] [TestCase(ScoringMode.Classic, HitResult.Meh, 50)] - [TestCase(ScoringMode.Classic, HitResult.Good, 200)] + [TestCase(ScoringMode.Classic, HitResult.Ok, 100)] [TestCase(ScoringMode.Classic, HitResult.Great, 300)] public void TestSingleOsuHit(ScoringMode scoringMode, HitResult hitResult, int expectedScore) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs index 253b8d9c55..377f305d63 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHitErrorMeter.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay Children = new[] { new OsuSpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" }, - new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" }, + new OsuSpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Ok)}" }, new OsuSpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" }, } }); diff --git a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs index 6f73a284a2..b58f65800d 100644 --- a/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs +++ b/osu.Game/Scoring/Legacy/ScoreInfoExtensions.cs @@ -28,37 +28,9 @@ namespace osu.Game.Scoring.Legacy } } - public static int? GetCount300(this ScoreInfo scoreInfo) - { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) - { - case 0: - case 1: - case 3: - return getCount(scoreInfo, HitResult.Great); + public static int? GetCount300(this ScoreInfo scoreInfo) => getCount(scoreInfo, HitResult.Great); - case 2: - return getCount(scoreInfo, HitResult.Perfect); - } - - return null; - } - - public static void SetCount300(this ScoreInfo scoreInfo, int value) - { - switch (scoreInfo.Ruleset?.ID ?? scoreInfo.RulesetID) - { - case 0: - case 1: - case 3: - scoreInfo.Statistics[HitResult.Great] = value; - break; - - case 2: - scoreInfo.Statistics[HitResult.Perfect] = value; - break; - } - } + public static void SetCount300(this ScoreInfo scoreInfo, int value) => scoreInfo.Statistics[HitResult.Great] = value; public static int? GetCountKatu(this ScoreInfo scoreInfo) { @@ -94,8 +66,6 @@ namespace osu.Game.Scoring.Legacy { case 0: case 1: - return getCount(scoreInfo, HitResult.Good); - case 3: return getCount(scoreInfo, HitResult.Ok); @@ -112,9 +82,6 @@ namespace osu.Game.Scoring.Legacy { case 0: case 1: - scoreInfo.Statistics[HitResult.Good] = value; - break; - case 3: scoreInfo.Statistics[HitResult.Ok] = value; break; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 5caf07b554..e38913b13a 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -336,7 +336,7 @@ namespace osu.Game.Skinning case HitResult.Meh: return this.GetAnimation("hit50", true, false); - case HitResult.Good: + case HitResult.Ok: return this.GetAnimation("hit100", true, false); case HitResult.Great: diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 704d01e479..9090a12d3f 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -35,8 +35,10 @@ namespace osu.Game.Tests Statistics[HitResult.Miss] = 1; Statistics[HitResult.Meh] = 50; - Statistics[HitResult.Good] = 100; + Statistics[HitResult.Ok] = 100; + Statistics[HitResult.Good] = 200; Statistics[HitResult.Great] = 300; + Statistics[HitResult.Perfect] = 320; Statistics[HitResult.SmallTickHit] = 50; Statistics[HitResult.SmallTickMiss] = 25; Statistics[HitResult.LargeTickHit] = 100; From 91262620d3c72bb14880d1e7262c734aa4672fd2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:17:06 +0900 Subject: [PATCH 048/164] Remove XMLDocs from Ok/Perfect hit results --- osu.Game/Rulesets/Scoring/HitResult.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 7a02db190a..d979c342e1 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -32,9 +32,6 @@ namespace osu.Game.Rulesets.Scoring [Order(4)] Meh, - /// - /// Optional judgement. - /// [Description(@"OK")] [Order(3)] Ok, @@ -47,9 +44,6 @@ namespace osu.Game.Rulesets.Scoring [Order(1)] Great, - /// - /// Optional judgement. - /// [Description(@"Perfect")] [Order(0)] Perfect, From 53b3d238427d456be3e5acbd79d221b7d1937136 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:26:49 +0900 Subject: [PATCH 049/164] Expose HitObjectComposer for other components in the Compose csreen to use --- .../Screens/Edit/Compose/ComposeScreen.cs | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index d7a4661fa0..5282b4d998 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -1,8 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Skinning; @@ -18,11 +22,23 @@ namespace osu.Game.Screens.Edit.Compose { } - protected override Drawable CreateMainContent() + private Ruleset ruleset; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + ruleset = parent.Get>().Value.BeatmapInfo.Ruleset?.CreateInstance(); composer = ruleset?.CreateHitObjectComposer(); + // make the composer available to the timeline and other components in this screen. + dependencies.CacheAs(composer); + + return dependencies; + } + + protected override Drawable CreateMainContent() + { if (ruleset == null || composer == null) return new ScreenWhiteBox.UnderConstructionMessage(ruleset == null ? "This beatmap" : $"{ruleset.Description}'s composer"); From f16fc2907188da26ff64cb7743eb1e0a2319f444 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 16:42:54 +0900 Subject: [PATCH 050/164] Add combo colour display support --- .../Timeline/TimelineHitObjectBlueprint.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b95b3842b3..7dfaf02af4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -15,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osuTK; using osuTK.Graphics; @@ -34,6 +36,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly List shadowComponents = new List(); + private DrawableHitObject drawableHitObject; + + private Bindable comboColour; + private const float thickness = 5; private const float shadow_radius = 5; @@ -123,6 +129,30 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline updateShadows(); } + [BackgroundDependencyLoader(true)] + private void load(HitObjectComposer composer) + { + if (composer != null) + { + // best effort to get the drawable representation for grabbing colour and what not. + drawableHitObject = composer.HitObjects.FirstOrDefault(d => d.HitObject == HitObject); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (drawableHitObject != null) + { + comboColour = drawableHitObject.AccentColour.GetBoundCopy(); + comboColour.BindValueChanged(colour => + { + Colour = drawableHitObject.AccentColour.Value; + }, true); + } + } + protected override void Update() { base.Update(); From 8d8d45a0c068a9348e1ba37eecdf673900dae70d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:04:59 +0900 Subject: [PATCH 051/164] Add combo index display support --- .../Timeline/TimelineHitObjectBlueprint.cs | 48 +++++++++++++++++-- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 7dfaf02af4..9aa0fee96e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -13,6 +13,8 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -40,11 +42,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable comboColour; + private readonly Container mainComponents; + + private readonly OsuSpriteText comboIndexText; + + private Bindable comboIndex; + private const float thickness = 5; private const float shadow_radius = 5; - private const float circle_size = 16; + private const float circle_size = 24; public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) @@ -60,6 +68,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + AddRangeInternal(new Drawable[] + { + mainComponents = new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + comboIndexText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: circle_size / 2, weight: FontWeight.Black), + }, + }); + circle = new Circle { Size = new Vector2(circle_size), @@ -83,7 +108,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline DragBar dragBarUnderlay; Container extensionBar; - AddRangeInternal(new Drawable[] + mainComponents.AddRange(new Drawable[] { extensionBar = new Container { @@ -123,7 +148,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } else { - AddInternal(circle); + mainComponents.Add(circle); } updateShadows(); @@ -143,12 +168,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); + if (HitObject is IHasComboInformation comboInfo) + { + comboIndex = comboInfo.IndexInCurrentComboBindable.GetBoundCopy(); + comboIndex.BindValueChanged(combo => + { + comboIndexText.Text = (combo.NewValue + 1).ToString(); + }, true); + } + if (drawableHitObject != null) { comboColour = drawableHitObject.AccentColour.GetBoundCopy(); comboColour.BindValueChanged(colour => { - Colour = drawableHitObject.AccentColour.Value; + mainComponents.Colour = drawableHitObject.AccentColour.Value; + + var col = mainComponents.Colour.AverageColour.Linear; + float brightness = col.R + col.G + col.B; + + // decide the combo index colour based on brightness? + comboIndexText.Colour = brightness > 0.5f ? Color4.Black : Color4.White; }, true); } } From c47652c97a22350e3d14269fc4d6936c2856e444 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:22:47 +0900 Subject: [PATCH 052/164] Add gradient to hide subtractive colour issues Good thing is looks better than without. --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 9aa0fee96e..11f44c59ac 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -8,6 +8,7 @@ using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; @@ -182,7 +183,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline comboColour = drawableHitObject.AccentColour.GetBoundCopy(); comboColour.BindValueChanged(colour => { - mainComponents.Colour = drawableHitObject.AccentColour.Value; + mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White); var col = mainComponents.Colour.AverageColour.Linear; float brightness = col.R + col.G + col.B; From 6e1ea004436fcdc09da0b6599d37a75e778b5a18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 29 Sep 2020 17:34:50 +0900 Subject: [PATCH 053/164] Don't apply gradient to non-duration objects --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 11f44c59ac..455f1e17bd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -91,9 +91,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Size = new Vector2(circle_size), Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - AlwaysPresent = true, - Colour = Color4.White, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -183,7 +180,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline comboColour = drawableHitObject.AccentColour.GetBoundCopy(); comboColour.BindValueChanged(colour => { - mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White); + if (HitObject is IHasDuration) + mainComponents.Colour = ColourInfo.GradientHorizontal(drawableHitObject.AccentColour.Value, Color4.White); + else + mainComponents.Colour = drawableHitObject.AccentColour.Value; var col = mainComponents.Colour.AverageColour.Linear; float brightness = col.R + col.G + col.B; From 379a4cca85bec432dc2fa18da48fbdd7e09e8158 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:48:44 +0900 Subject: [PATCH 054/164] Adjust hold note tests --- .../TestSceneHoldNoteInput.cs | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 95072cf4f8..5cb1519196 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); - assertNoteJudgement(HitResult.Perfect); + assertNoteJudgement(HitResult.IgnoreHit); } /// @@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); } @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); } @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Miss); } @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Perfect); } @@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Miss); } @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Miss); } @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Perfect); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Meh); } @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Miss); } @@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Perfect); + assertTickJudgement(HitResult.LargeTickHit); assertTailJudgement(HitResult.Meh); } @@ -235,7 +235,7 @@ namespace osu.Game.Rulesets.Mania.Tests }); assertHeadJudgement(HitResult.Miss); - assertTickJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); assertTailJudgement(HitResult.Meh); } @@ -280,10 +280,10 @@ namespace osu.Game.Rulesets.Mania.Tests }, beatmap); AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject)) - .All(j => j.Type == HitResult.Miss)); + .All(j => !j.Type.IsHit())); AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject)) - .All(j => j.Type == HitResult.Perfect)); + .All(j => j.Type.IsHit())); } private void assertHeadJudgement(HitResult result) From cc9fa4675c00a905272c63d2e963d48d0fb2b94a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 17:59:42 +0900 Subject: [PATCH 055/164] Adjust HP increases --- osu.Game/Rulesets/Judgements/Judgement.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index ea7a8d8d25..d80ebfe402 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -124,19 +124,19 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Meh: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.Ok: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.01; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; case HitResult.Good: - return DEFAULT_MAX_HEALTH_INCREASE * 0.5; + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; case HitResult.Great: - return DEFAULT_MAX_HEALTH_INCREASE; + return DEFAULT_MAX_HEALTH_INCREASE * 0.8; case HitResult.Perfect: - return DEFAULT_MAX_HEALTH_INCREASE * 1.05; + return DEFAULT_MAX_HEALTH_INCREASE; case HitResult.SmallBonus: return DEFAULT_MAX_HEALTH_INCREASE * 0.1; From 4c872094c9a3eb1ff64468b30f999c486ae8d73f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 18:29:50 +0900 Subject: [PATCH 056/164] Adjust slider tests --- osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs | 10 +++++----- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs index 854626d362..32a36ab317 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOutOfOrderHits.cs @@ -209,9 +209,9 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.IgnoreHit); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Miss); - addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } /// @@ -252,9 +252,9 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.IgnoreHit); addJudgementAssert("slider head", () => ((Slider)hitObjects[1]).HeadCircle, HitResult.Great); - addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.Great); + addJudgementAssert("slider tick", () => ((Slider)hitObjects[1]).NestedHitObjects[1] as SliderTick, HitResult.LargeTickHit); } /// @@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); addJudgementAssert(hitObjects[0], HitResult.Great); - addJudgementAssert(hitObjects[1], HitResult.Great); + addJudgementAssert(hitObjects[1], HitResult.IgnoreHit); } private void addJudgementAssert(OsuHitObject hitObject, HitResult result) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index b543b6fa94..3d8a52a864 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -312,13 +312,13 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Tracking dropped", assertMidSliderJudgementFail); } - private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == HitResult.Great); + private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type.IsHit()); - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; - private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.Great; + private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit; - private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.Miss; + private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.IgnoreMiss; private ScoreAccessibleReplayPlayer currentPlayer; From 297168ecc41e7dd053fac1d0a17c0ac898315e79 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 29 Sep 2020 18:55:06 +0900 Subject: [PATCH 057/164] Fix scores sometimes not being re-standardised correctly --- .../Requests/Responses/APILegacyScoreInfo.cs | 1 + osu.Game/Scoring/ScoreInfo.cs | 28 +++++++++++++++ osu.Game/Scoring/ScoreManager.cs | 34 ++++++++++++++----- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index b941cd8973..3d3c07a5ad 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -38,6 +38,7 @@ namespace osu.Game.Online.API.Requests.Responses Rank = Rank, Ruleset = ruleset, Mods = mods, + IsLegacyScore = true }; if (Statistics != null) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 4ed3f92e25..0206989231 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -185,6 +185,34 @@ namespace osu.Game.Scoring [JsonProperty("position")] public int? Position { get; set; } + private bool isLegacyScore; + + /// + /// Whether this represents a legacy (osu!stable) score. + /// + [JsonIgnore] + [NotMapped] + public bool IsLegacyScore + { + get + { + if (isLegacyScore) + return true; + + // The above check will catch legacy online scores that have an appropriate UserString + UserId. + // For non-online scores such as those imported in, a heuristic is used based on the following table: + // + // Mode | UserString | UserId + // --------------- | ---------- | --------- + // stable | | 1 + // lazer | | + // lazer (offline) | Guest | 1 + + return ID > 0 && UserID == 1 && UserString != "Guest"; + } + set => isLegacyScore = value; + } + public IEnumerable<(HitResult result, int count, int? maxCount)> GetStatisticsForDisplay() { foreach (var key in OrderAttributeUtils.GetValuesInOrder()) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 561ca631b3..8e8147ff39 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -10,6 +10,7 @@ using System.Threading; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -149,23 +150,38 @@ namespace osu.Game.Scoring return; } - int? beatmapMaxCombo = score.Beatmap.MaxCombo; + int beatmapMaxCombo; - if (beatmapMaxCombo == null) + if (score.IsLegacyScore) { - if (score.Beatmap.ID == 0 || difficulties == null) + // This score is guaranteed to be an osu!stable score. + // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. + if (score.Beatmap.MaxCombo == null) { - // We don't have enough information (max combo) to compute the score, so let's use the provided score. - Value = score.TotalScore; + if (score.Beatmap.ID == 0 || difficulties == null) + { + // We don't have enough information (max combo) to compute the score, so use the provided score. + Value = score.TotalScore; + return; + } + + // We can compute the max combo locally after the async beatmap difficulty computation. + difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); + difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); + return; } - // We can compute the max combo locally after the async beatmap difficulty computation. - difficultyBindable = difficulties().GetBindableDifficulty(score.Beatmap, score.Ruleset, score.Mods, (difficultyCancellationSource = new CancellationTokenSource()).Token); - difficultyBindable.BindValueChanged(d => updateScore(d.NewValue.MaxCombo), true); + beatmapMaxCombo = score.Beatmap.MaxCombo.Value; } else - updateScore(beatmapMaxCombo.Value); + { + // This score is guaranteed to be an osu!lazer score. + // The combo must be determined through the score's statistics, as both the beatmap's max combo and the difficulty calculator will provide osu!stable combo values. + beatmapMaxCombo = Enum.GetValues(typeof(HitResult)).OfType().Where(r => r.AffectsCombo()).Select(r => score.Statistics.GetOrDefault(r)).Sum(); + } + + updateScore(beatmapMaxCombo); } private void updateScore(int beatmapMaxCombo) From 414c40d29849932734343123a8b89bf0725381c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:45:14 +0900 Subject: [PATCH 058/164] Reverse inheritance order of SkinnableSound's pause logic --- .../Objects/Drawables/DrawableSlider.cs | 4 +- .../Objects/Drawables/DrawableSpinner.cs | 4 +- .../Audio/DrumSampleContainer.cs | 8 +-- .../Gameplay/TestSceneSkinnableSound.cs | 4 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 16 ++--- .../Objects/Drawables/DrawableHitObject.cs | 4 +- .../Screens/Play/ISamplePlaybackDisabler.cs | 2 +- osu.Game/Screens/Play/PauseOverlay.cs | 12 +--- osu.Game/Skinning/PausableSkinnableSound.cs | 66 +++++++++++++++++++ osu.Game/Skinning/SkinnableSound.cs | 50 +------------- 10 files changed, 91 insertions(+), 79 deletions(-) create mode 100644 osu.Game/Skinning/PausableSkinnableSound.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 68f203db47..b73ae5eeb9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking.BindValueChanged(updateSlidingSample); } - private SkinnableSound slidingSample; + private PausableSkinnableSound slidingSample; protected override void LoadSamples() { @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "sliderslide"; - AddInternal(slidingSample = new SkinnableSound(clone) + AddInternal(slidingSample = new PausableSkinnableSound(clone) { Looping = true }); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index b2a706833c..6488c60acf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables isSpinning.BindValueChanged(updateSpinningSample); } - private SkinnableSound spinningSample; + private PausableSkinnableSound spinningSample; private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var clone = HitObject.SampleControlPoint.ApplyTo(firstSample); clone.Name = "spinnerspin"; - AddInternal(spinningSample = new SkinnableSound(clone) + AddInternal(spinningSample = new PausableSkinnableSound(clone) { Volume = { Value = 0 }, Looping = true, diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs index 7c39c040b1..fcf7c529f5 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs @@ -42,9 +42,9 @@ namespace osu.Game.Rulesets.Taiko.Audio } } - private SkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) + private PausableSkinnableSound addSound(HitSampleInfo hitSampleInfo, double lifetimeStart, double lifetimeEnd) { - var drawable = new SkinnableSound(hitSampleInfo) + var drawable = new PausableSkinnableSound(hitSampleInfo) { LifetimeStart = lifetimeStart, LifetimeEnd = lifetimeEnd @@ -57,8 +57,8 @@ namespace osu.Game.Rulesets.Taiko.Audio public class DrumSample { - public SkinnableSound Centre; - public SkinnableSound Rim; + public PausableSkinnableSound Centre; + public PausableSkinnableSound Rim; } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs index 8b37cbd06f..8f2011e5dd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay private GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private TestSkinSourceContainer skinSource; - private SkinnableSound skinnableSound; + private PausableSkinnableSound skinnableSound; [SetUp] public void SetUp() => Schedule(() => @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Clock = gameplayClock, RelativeSizeAxes = Axes.Both, - Child = skinnableSound = new SkinnableSound(new SampleInfo("normal-sliderslide")) + Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide")) }, }; }); diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 4004953cd1..282de3a8e1 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mods public class NightcoreBeatContainer : BeatSyncedContainer { - private SkinnableSound hatSample; - private SkinnableSound clapSample; - private SkinnableSound kickSample; - private SkinnableSound finishSample; + private PausableSkinnableSound hatSample; + private PausableSkinnableSound clapSample; + private PausableSkinnableSound kickSample; + private PausableSkinnableSound finishSample; private int? firstBeat; @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Mods { InternalChildren = new Drawable[] { - hatSample = new SkinnableSound(new SampleInfo("nightcore-hat")), - clapSample = new SkinnableSound(new SampleInfo("nightcore-clap")), - kickSample = new SkinnableSound(new SampleInfo("nightcore-kick")), - finishSample = new SkinnableSound(new SampleInfo("nightcore-finish")), + hatSample = new PausableSkinnableSound(new SampleInfo("nightcore-hat")), + clapSample = new PausableSkinnableSound(new SampleInfo("nightcore-clap")), + kickSample = new PausableSkinnableSound(new SampleInfo("nightcore-kick")), + finishSample = new PausableSkinnableSound(new SampleInfo("nightcore-finish")), }; } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 796b8f7aae..59a3381b8b 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public readonly Bindable AccentColour = new Bindable(Color4.Gray); - protected SkinnableSound Samples { get; private set; } + protected PausableSkinnableSound Samples { get; private set; } public virtual IEnumerable GetSamples() => HitObject.Samples; @@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Objects.Drawables + $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}."); } - Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); + Samples = new PausableSkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s))); AddInternal(Samples); } diff --git a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs index 83e89d654b..6b37021fe6 100644 --- a/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs +++ b/osu.Game/Screens/Play/ISamplePlaybackDisabler.cs @@ -8,7 +8,7 @@ namespace osu.Game.Screens.Play { /// /// Allows a component to disable sample playback dynamically as required. - /// Handled by . + /// Handled by . /// public interface ISamplePlaybackDisabler { diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 9494971f8a..65f34aba3e 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - AddInternal(pauseLoop = new UnpausableSkinnableSound(new SampleInfo("pause-loop")) + AddInternal(pauseLoop = new SkinnableSound(new SampleInfo("pause-loop")) { Looping = true, Volume = { Value = 0 } @@ -54,15 +54,5 @@ namespace osu.Game.Screens.Play pauseLoop.VolumeTo(0, TRANSITION_DURATION, Easing.OutQuad).Finally(_ => pauseLoop.Stop()); } - - private class UnpausableSkinnableSound : SkinnableSound - { - protected override bool PlayWhenPaused => true; - - public UnpausableSkinnableSound(SampleInfo sampleInfo) - : base(sampleInfo) - { - } - } } } diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs new file mode 100644 index 0000000000..991278fb98 --- /dev/null +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Audio; +using osu.Game.Screens.Play; + +namespace osu.Game.Skinning +{ + public class PausableSkinnableSound : SkinnableSound + { + protected bool RequestedPlaying { get; private set; } + + public PausableSkinnableSound(ISampleInfo hitSamples) + : base(hitSamples) + { + } + + public PausableSkinnableSound(IEnumerable hitSamples) + : base(hitSamples) + { + } + + private readonly IBindable samplePlaybackDisabled = new Bindable(); + + [BackgroundDependencyLoader(true)] + private void load(ISamplePlaybackDisabler samplePlaybackDisabler) + { + // if in a gameplay context, pause sample playback when gameplay is paused. + if (samplePlaybackDisabler != null) + { + samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + samplePlaybackDisabled.BindValueChanged(disabled => + { + if (RequestedPlaying) + { + if (disabled.NewValue) + base.Stop(); + // it's not easy to know if a sample has finished playing (to end). + // to keep things simple only resume playing looping samples. + else if (Looping) + base.Play(); + } + }); + } + } + + public override void Play() + { + RequestedPlaying = true; + + if (samplePlaybackDisabled.Value) + return; + + base.Play(); + } + + public override void Stop() + { + RequestedPlaying = false; + base.Stop(); + } + } +} diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index f3ab8b86bc..91bdcd7444 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -11,7 +11,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Game.Audio; -using osu.Game.Screens.Play; namespace osu.Game.Skinning { @@ -22,8 +21,6 @@ namespace osu.Game.Skinning [Resolved] private ISampleStore samples { get; set; } - private bool requestedPlaying; - public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; @@ -37,8 +34,6 @@ namespace osu.Game.Skinning /// protected bool PlayWhenZeroVolume => Looping; - protected virtual bool PlayWhenPaused => false; - private readonly AudioContainer samplesContainer; public SkinnableSound(ISampleInfo hitSamples) @@ -52,30 +47,6 @@ namespace osu.Game.Skinning InternalChild = samplesContainer = new AudioContainer(); } - private readonly IBindable samplePlaybackDisabled = new Bindable(); - - [BackgroundDependencyLoader(true)] - private void load(ISamplePlaybackDisabler samplePlaybackDisabler) - { - // if in a gameplay context, pause sample playback when gameplay is paused. - if (samplePlaybackDisabler != null) - { - samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); - samplePlaybackDisabled.BindValueChanged(disabled => - { - if (requestedPlaying) - { - if (disabled.NewValue && !PlayWhenPaused) - stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) - play(); - } - }); - } - } - private bool looping; public bool Looping @@ -91,17 +62,8 @@ namespace osu.Game.Skinning } } - public void Play() + public virtual void Play() { - requestedPlaying = true; - play(); - } - - private void play() - { - if (samplePlaybackDisabled.Value && !PlayWhenPaused) - return; - samplesContainer.ForEach(c => { if (PlayWhenZeroVolume || c.AggregateVolume.Value > 0) @@ -109,13 +71,7 @@ namespace osu.Game.Skinning }); } - public void Stop() - { - requestedPlaying = false; - stop(); - } - - private void stop() + public virtual void Stop() { samplesContainer.ForEach(c => c.Stop()); } @@ -150,7 +106,7 @@ namespace osu.Game.Skinning // Start playback internally for the new samples if the previous ones were playing beforehand. if (wasPlaying) - play(); + Play(); } #region Re-expose AudioContainer From 6cceb42ad5f21861b839fca68f358183ad1b3da6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 15:50:53 +0900 Subject: [PATCH 059/164] Remove unused DI resolution --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 59a3381b8b..eb12c1cdfc 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; -using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -359,9 +358,6 @@ namespace osu.Game.Rulesets.Objects.Drawables { } - [Resolved(canBeNull: true)] - private ISamplePlaybackDisabler samplePlaybackDisabler { get; set; } - /// /// Calculate the position to be used for sample playback at a specified X position (0..1). /// From a40c2ea5ee54863c0991c5be9fdcfc5fa76cbc03 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:02:22 +0900 Subject: [PATCH 060/164] Simplify control point group binding/update logic --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index d7da29218f..1d550b7cc3 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Edit.Timing private OsuButton deleteButton; private ControlPointTable table; - private IBindableList controlGroups; + private BindableList controlGroups; [Resolved] private EditorClock clock { get; set; } @@ -128,12 +128,14 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); - controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - controlGroups.CollectionChanged += (sender, args) => createContent(); - createContent(); - } + // todo: remove cast after https://github.com/ppy/osu-framework/pull/3906 is merged + controlGroups = (BindableList)Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - private void createContent() => table.ControlGroups = controlGroups; + controlGroups.BindCollectionChanged((sender, args) => + { + table.ControlGroups = controlGroups; + }, true); + } private void delete() { From 2ef09a8730085609f3ecb5eeba827e5a15554f60 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:06:58 +0900 Subject: [PATCH 061/164] Populate test scene with control points --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 09f5ac2224..cda4ade6e1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; @@ -19,7 +20,7 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneTimingScreen() { - editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); } [BackgroundDependencyLoader] From 1dd354120b1013759d210e49902e08393c6d18b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:16:14 +0900 Subject: [PATCH 062/164] Fix beatmap potentially changing in test scene --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index cda4ade6e1..04cdfd7a67 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; @@ -27,7 +26,15 @@ namespace osu.Game.Tests.Visual.Editing private void load() { Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); + Beatmap.Disabled = true; + Child = new TimingScreen(); } + + protected override void Dispose(bool isDisposing) + { + Beatmap.Disabled = false; + base.Dispose(isDisposing); + } } } From e760ed8e01921c42c6adc6e8039b475f0b5758b0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:39:02 +0900 Subject: [PATCH 063/164] Fix scroll wheel being handled by base test scene --- osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs | 2 ++ osu.Game/Tests/Visual/EditorClockTestScene.cs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 04cdfd7a67..b82e776164 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + protected override bool ScrollUsingMouseWheel => false; + public TestSceneTimingScreen() { editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index f0ec638fc9..693c9cb792 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -20,6 +20,8 @@ namespace osu.Game.Tests.Visual protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); protected new readonly EditorClock Clock; + protected virtual bool ScrollUsingMouseWheel => true; + protected EditorClockTestScene() { Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; @@ -57,6 +59,9 @@ namespace osu.Game.Tests.Visual protected override bool OnScroll(ScrollEvent e) { + if (!ScrollUsingMouseWheel) + return false; + if (e.ScrollDelta.Y > 0) Clock.SeekBackward(true); else From 5b200a8ca41f44084a3c0cc3703b50042a76a3bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:39:27 +0900 Subject: [PATCH 064/164] Change default zoom of timing screen timeline to most zoomed out --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 10 +++++----- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 10 +++++++++- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 7 +++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index b99a053859..d870eb5279 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -14,9 +14,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public class TimelineArea : Container { - private readonly Timeline timeline = new Timeline { RelativeSizeAxes = Axes.Both }; + public readonly Timeline Timeline = new Timeline { RelativeSizeAxes = Axes.Both }; - protected override Container Content => timeline; + protected override Container Content => Timeline; [BackgroundDependencyLoader] private void load() @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } }, - timeline + Timeline }, }, ColumnDimensions = new[] @@ -121,9 +121,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformCheckbox.Current.Value = true; - timeline.WaveformVisible.BindTo(waveformCheckbox.Current); + Timeline.WaveformVisible.BindTo(waveformCheckbox.Current); } - private void changeZoom(float change) => timeline.Zoom += change; + private void changeZoom(float change) => Timeline.Zoom += change; } } diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index 34eddbefad..d6d782e70c 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -115,10 +115,18 @@ namespace osu.Game.Screens.Edit new TimelineTickDisplay(), CreateTimelineContent(), } - }, timelineContainer.Add); + }, t => + { + timelineContainer.Add(t); + OnTimelineLoaded(t); + }); }); } + protected virtual void OnTimelineLoaded(TimelineArea timelineArea) + { + } + protected abstract Drawable CreateMainContent(); protected virtual Drawable CreateTimelineContent() => new Container(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 1d550b7cc3..66c1f4895b 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; namespace osu.Game.Screens.Edit.Timing @@ -58,6 +59,12 @@ namespace osu.Game.Screens.Edit.Timing }); } + protected override void OnTimelineLoaded(TimelineArea timelineArea) + { + base.OnTimelineLoaded(timelineArea); + timelineArea.Timeline.Zoom = timelineArea.Timeline.MinZoom; + } + public class ControlPointList : CompositeDrawable { private OsuButton deleteButton; From 698042268ff4463a4b18544b68e3dea806c6524a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 16:58:19 +0900 Subject: [PATCH 065/164] Show control points in timing screen timeline --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 66c1f4895b..4ab27ff1c0 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -30,6 +31,11 @@ namespace osu.Game.Screens.Edit.Timing { } + protected override Drawable CreateTimelineContent() => new ControlPointPart + { + RelativeSizeAxes = Axes.Both, + }; + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, From 3422db1bb291f7daf0b42887edb24a54ac3637b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 17:10:22 +0900 Subject: [PATCH 066/164] Use top-left colour for deciding the text colour (gradient was added in some cases) --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 455f1e17bd..bc2ccfc605 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -185,7 +185,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline else mainComponents.Colour = drawableHitObject.AccentColour.Value; - var col = mainComponents.Colour.AverageColour.Linear; + var col = mainComponents.Colour.TopLeft.Linear; float brightness = col.R + col.G + col.B; // decide the combo index colour based on brightness? From b0f8e11bd46b54ab14ee678a10b576daa7ed4278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 17:34:14 +0900 Subject: [PATCH 067/164] Fix incorrect caching --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index e404d7ca42..9f8e55f577 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Play /// public GameplayClock GameplayClock => localGameplayClock; - [Cached] + [Cached(typeof(GameplayClock))] [Cached(typeof(ISamplePlaybackDisabler))] private readonly LocalGameplayClock localGameplayClock; From bc943dee53e7208e57aa1dc77f38bfcac3e818a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 17:52:12 +0900 Subject: [PATCH 068/164] Add textbox entry for speed multiplier and volume --- .../Screens/Edit/Timing/DifficultySection.cs | 12 +-- osu.Game/Screens/Edit/Timing/SampleSection.cs | 12 ++- .../Edit/Timing/SliderWithTextBoxInput.cs | 74 +++++++++++++++++++ 3 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 58a7f97e5f..78766d9777 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,27 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class DifficultySection : Section { - private SettingsSlider multiplier; + private SliderWithTextBoxInput multiplierSlider; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - multiplier = new SettingsSlider + multiplierSlider = new SliderWithTextBoxInput("Speed Multiplier") { - LabelText = "Speed Multiplier", - Bindable = new DifficultyControlPoint().SpeedMultiplierBindable, - RelativeSizeAxes = Axes.X, + Current = new DifficultyControlPoint().SpeedMultiplierBindable } }); } @@ -31,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { - multiplier.Bindable = point.NewValue.SpeedMultiplierBindable; + multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable; } } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 4665c77991..de986e28ca 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,18 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class SampleSection : Section { private LabelledTextBox bank; - private SettingsSlider volume; + private SliderWithTextBoxInput volume; [BackgroundDependencyLoader] private void load() @@ -24,10 +23,9 @@ namespace osu.Game.Screens.Edit.Timing { Label = "Bank Name", }, - volume = new SettingsSlider + volume = new SliderWithTextBoxInput("Volume") { - Bindable = new SampleControlPoint().SampleVolumeBindable, - LabelText = "Volume", + Current = new SampleControlPoint().SampleVolumeBindable, } }); } @@ -37,7 +35,7 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { bank.Current = point.NewValue.SampleBankBindable; - volume.Bindable = point.NewValue.SampleVolumeBindable; + volume.Current = point.NewValue.SampleVolumeBindable; } } diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs new file mode 100644 index 0000000000..07b914c506 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class SliderWithTextBoxInput : CompositeDrawable, IHasCurrentValue + where T : struct, IEquatable, IComparable, IConvertible + { + private readonly SettingsSlider slider; + + public SliderWithTextBoxInput(string labelText) + { + LabelledTextBox textbox; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + textbox = new LabelledTextBox + { + Label = labelText, + }, + slider = new SettingsSlider + { + RelativeSizeAxes = Axes.X, + } + } + }, + }; + + textbox.OnCommit += (t, isNew) => + { + if (!isNew) return; + + try + { + slider.Bindable.Parse(t.Text); + } + catch + { + // will restore the previous text value on failure. + Current.TriggerChange(); + } + }; + + Current.BindValueChanged(val => + { + textbox.Text = val.NewValue.ToString(); + }, true); + } + + public Bindable Current + { + get => slider.Bindable; + set => slider.Bindable = value; + } + } +} From 44fc0c672304486c7706334ab990802f03aaa793 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 18:08:55 +0900 Subject: [PATCH 069/164] Fix default value of bpm being too high --- osu.Game/Screens/Edit/Timing/TimingSection.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 879363ba08..cc79dd2acc 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -103,12 +103,17 @@ namespace osu.Game.Screens.Edit.Timing private const double sane_maximum = 240; private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; - private readonly BindableDouble bpmBindable = new BindableDouble(); + + private readonly BindableDouble bpmBindable = new BindableDouble(60000 / TimingControlPoint.DEFAULT_BEAT_LENGTH) + { + MinValue = sane_minimum, + MaxValue = sane_maximum, + }; public BPMSlider() { beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true); - bpmBindable.BindValueChanged(bpm => bpmBindable.Default = beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); base.Bindable = bpmBindable; } From 5242f5648dd26d89829d130f78459589f220434b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 18:34:13 +0900 Subject: [PATCH 070/164] Fix timeline control point display not updating with changes --- .../Summary/Parts/ControlPointPart.cs | 70 +++++++------------ .../Summary/Parts/GroupVisualisation.cs | 46 ++++++++++++ 2 files changed, 71 insertions(+), 45 deletions(-) create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 102955657e..6edb49ba1b 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -1,70 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Specialized; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { /// /// The part of the timeline that displays the control points. /// - public class ControlPointPart : TimelinePart + public class ControlPointPart : TimelinePart { + private BindableList controlPointGroups; + protected override void LoadBeatmap(WorkingBeatmap beatmap) { base.LoadBeatmap(beatmap); - ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo; - - cpi.TimingPoints.ForEach(addTimingPoint); - - // Consider all non-timing points as the same type - cpi.SamplePoints.Select(c => (ControlPoint)c) - .Concat(cpi.EffectPoints) - .Concat(cpi.DifficultyPoints) - .Distinct() - // Non-timing points should not be added where there are timing points - .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time) - .ForEach(addNonTimingPoint); - } - - private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint)); - private void addNonTimingPoint(ControlPoint controlPoint) => Add(new NonTimingPointVisualisation(controlPoint)); - - private class TimingPointVisualisation : ControlPointVisualisation - { - public TimingPointVisualisation(ControlPoint controlPoint) - : base(controlPoint) + controlPointGroups = (BindableList)beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups.BindCollectionChanged((sender, args) => { - } + switch (args.Action) + { + case NotifyCollectionChangedAction.Reset: + Clear(); + break; - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.YellowDark; - } + case NotifyCollectionChangedAction.Add: + foreach (var group in args.NewItems.OfType()) + Add(new GroupVisualisation(group)); + break; - private class NonTimingPointVisualisation : ControlPointVisualisation - { - public NonTimingPointVisualisation(ControlPoint controlPoint) - : base(controlPoint) - { - } + case NotifyCollectionChangedAction.Remove: + foreach (var group in args.OldItems.OfType()) + { + var matching = Children.SingleOrDefault(gv => gv.Group == group); - [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Green; - } + matching?.Expire(); + } - private abstract class ControlPointVisualisation : PointVisualisation - { - protected ControlPointVisualisation(ControlPoint controlPoint) - : base(controlPoint.Time) - { - } + break; + } + }, true); } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs new file mode 100644 index 0000000000..b9eb53b697 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + public class GroupVisualisation : PointVisualisation + { + public readonly ControlPointGroup Group; + + private BindableList controlPoints; + + [Resolved] + private OsuColour colours { get; set; } + + public GroupVisualisation(ControlPointGroup group) + : base(group.Time) + { + Group = group; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); + controlPoints.BindCollectionChanged((_, __) => + { + if (controlPoints.Count == 0) + { + Colour = Color4.Transparent; + return; + } + + Colour = controlPoints.Any(c => c is TimingControlPoint) ? colours.YellowDark : colours.Green; + }, true); + } + } +} From fab11a8241e470cd428d33a9084e0c927242c542 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 18:36:40 +0900 Subject: [PATCH 071/164] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index dc3e14c141..afc5d4ec52 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6412f707d0..5fa1685d9b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index f1e13169a5..60708a39e2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From a11c74d60044d0ba9ee39e7a1e8e91674577b3cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 20:27:02 +0900 Subject: [PATCH 072/164] Update to consume framework fixes --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 4ab27ff1c0..0a0cfe193d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing private OsuButton deleteButton; private ControlPointTable table; - private BindableList controlGroups; + private IBindableList controlGroups; [Resolved] private EditorClock clock { get; set; } @@ -141,8 +141,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); - // todo: remove cast after https://github.com/ppy/osu-framework/pull/3906 is merged - controlGroups = (BindableList)Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); controlGroups.BindCollectionChanged((sender, args) => { From fa742a2ef1567072065c8bb3d7b8de7ccf5def1d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Sep 2020 20:28:02 +0900 Subject: [PATCH 073/164] Update to consume framework fixes --- .../Components/Timelines/Summary/Parts/ControlPointPart.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 6edb49ba1b..8c0e31c04c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -14,13 +14,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class ControlPointPart : TimelinePart { - private BindableList controlPointGroups; + private IBindableList controlPointGroups; protected override void LoadBeatmap(WorkingBeatmap beatmap) { base.LoadBeatmap(beatmap); - controlPointGroups = (BindableList)beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) From 77651be2caff6e95fb52801462130197d9e02183 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Sep 2020 21:32:50 +0900 Subject: [PATCH 074/164] Remove padding from HitResult --- osu.Game/Rulesets/Scoring/HitResult.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index fc33ff9df2..2b9b1a6c8e 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"")] [Order(14)] - None = 0, + None, /// /// Indicates that the object has been judged as a miss. @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Scoring /// [Description(@"Miss")] [Order(5)] - Miss = 64, + Miss, [Description(@"Meh")] [Order(4)] @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates small tick miss. /// [Order(11)] - SmallTickMiss = 128, + SmallTickMiss, /// /// Indicates a small tick hit. @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [Order(10)] - LargeTickMiss = 192, + LargeTickMiss, /// /// Indicates a large tick hit. @@ -84,20 +84,20 @@ namespace osu.Game.Rulesets.Scoring /// [Description("S Bonus")] [Order(9)] - SmallBonus = 254, + SmallBonus, /// /// Indicates a large bonus. /// [Description("L Bonus")] [Order(8)] - LargeBonus = 320, + LargeBonus, /// /// Indicates a miss that should be ignored for scoring purposes. /// [Order(13)] - IgnoreMiss = 384, + IgnoreMiss, /// /// Indicates a hit that should be ignored for scoring purposes. From ca9f5b447ee643dc3ebe558b9e21707e1e41d2e3 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Thu, 1 Oct 2020 02:02:27 +0700 Subject: [PATCH 075/164] Fix UserListPanel background position --- osu.Game/Users/UserListPanel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Users/UserListPanel.cs b/osu.Game/Users/UserListPanel.cs index 9c95eff739..cc4fca9b94 100644 --- a/osu.Game/Users/UserListPanel.cs +++ b/osu.Game/Users/UserListPanel.cs @@ -26,6 +26,8 @@ namespace osu.Game.Users private void load() { Background.Width = 0.5f; + Background.Origin = Anchor.CentreRight; + Background.Anchor = Anchor.CentreRight; Background.Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(1), Color4.White.Opacity(0.3f)); } From 6b416c9881ab19dcc2291849777f729b6a422e57 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:09:12 +0900 Subject: [PATCH 076/164] Rename method and improve method implementation --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 3d8a52a864..d5c3538c81 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, }); - AddAssert("Tracking retained", assertGreatJudge); + AddAssert("Tracking retained", assertMaxJudge); } /// @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, }); - AddAssert("Tracking retained", assertGreatJudge); + AddAssert("Tracking retained", assertMaxJudge); } /// @@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, }); - AddAssert("Tracking retained", assertGreatJudge); + AddAssert("Tracking retained", assertMaxJudge); } /// @@ -288,7 +288,7 @@ namespace osu.Game.Rulesets.Osu.Tests new OsuReplayFrame { Position = new Vector2(slider_path_length, OsuHitObject.OBJECT_RADIUS * 1.199f), Actions = { OsuAction.LeftButton }, Time = time_slider_end }, }); - AddAssert("Tracking kept", assertGreatJudge); + AddAssert("Tracking kept", assertMaxJudge); } /// @@ -312,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("Tracking dropped", assertMidSliderJudgementFail); } - private bool assertGreatJudge() => judgementResults.Any() && judgementResults.All(t => t.Type.IsHit()); + private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult); private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; From 806d8b4b1dddcc35bb7512dd60cc078a6000e95f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:13:24 +0900 Subject: [PATCH 077/164] Make scoring int-based again --- osu.Game/Rulesets/Judgements/Judgement.cs | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index ea7a8d8d25..c7d572b629 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.Judgements /// /// The score awarded for a small bonus. /// - public const double SMALL_BONUS_SCORE = 10; + public const int SMALL_BONUS_SCORE = 10; /// /// The score awarded for a large bonus. /// - public const double LARGE_BONUS_SCORE = 50; + public const int LARGE_BONUS_SCORE = 50; /// /// The default health increase for a maximum judgement, as a proportion of total health. @@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Judgements /// /// The numeric score representation for the maximum achievable result. /// - public double MaxNumericResult => ToNumericResult(MaxResult); + public int MaxNumericResult => ToNumericResult(MaxResult); /// /// The health increase for the maximum achievable result. @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Judgements /// /// The to find the numeric score representation for. /// The numeric score representation of . - public double NumericResultFor(JudgementResult result) => ToNumericResult(result.Type); + public int NumericResultFor(JudgementResult result) => ToNumericResult(result.Type); /// /// Retrieves the numeric health increase of a . @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Judgements public override string ToString() => $"MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; - public static double ToNumericResult(HitResult result) + public static int ToNumericResult(HitResult result) { switch (result) { @@ -163,25 +163,25 @@ namespace osu.Game.Rulesets.Judgements return 0; case HitResult.SmallTickHit: - return 1 / 30d; + return 10; case HitResult.LargeTickHit: - return 1 / 10d; + return 30; case HitResult.Meh: - return 1 / 6d; + return 50; case HitResult.Ok: - return 1 / 3d; + return 100; case HitResult.Good: - return 2 / 3d; + return 200; case HitResult.Great: - return 1d; + return 300; case HitResult.Perfect: - return 7 / 6d; + return 350; case HitResult.SmallBonus: return SMALL_BONUS_SCORE; From 3a26bd8d9ba87590f2e6f8d35afd9c255f543979 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:14:16 +0900 Subject: [PATCH 078/164] Adjust obsoletion + xmldoc of NumericResultFor() --- osu.Game/Rulesets/Judgements/Judgement.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index c7d572b629..738ae0d6d2 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -82,12 +82,12 @@ namespace osu.Game.Rulesets.Judgements public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); /// - /// Retrieves the numeric score representation of a . + /// Retrieves the numeric score representation of a . /// - /// The to find the numeric score representation for. + /// The to find the numeric score representation for. /// The numeric score representation of . - [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be removed 20210328 - protected virtual int NumericResultFor(HitResult result) => result == HitResult.Miss ? 0 : 1; + [Obsolete("Has no effect. Use ToNumericResult(HitResult) (standardised across all rulesets).")] // Can be made non-virtual 20210328 + protected virtual int NumericResultFor(HitResult result) => ToNumericResult(result); /// /// Retrieves the numeric score representation of a . From 3c9ee6abc11f227eb6a18d10b66f0c7e6aedf04b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:15:34 +0900 Subject: [PATCH 079/164] Use local static to determine score per spinner tick --- .../Objects/Drawables/Pieces/SpinnerBonusDisplay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs index 1668cd73bd..f483bb1b26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBonusDisplay.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Judgements; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { @@ -14,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// public class SpinnerBonusDisplay : CompositeDrawable { + private static readonly int score_per_tick = new SpinnerBonusTick().CreateJudgement().MaxNumericResult; + private readonly OsuSpriteText bonusCounter; public SpinnerBonusDisplay() @@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; displayedCount = count; - bonusCounter.Text = $"{Judgement.LARGE_BONUS_SCORE * count}"; + bonusCounter.Text = $"{score_per_tick * count}"; bonusCounter.FadeOutFromOne(1500); bonusCounter.ScaleTo(1.5f).Then().ScaleTo(1f, 1000, Easing.OutQuint); } From c9f38f7bb6af116fa7ec813ceb3f100dcad30adb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 12:28:33 +0900 Subject: [PATCH 080/164] Add obsoletion notice --- .../Rulesets/Objects/Drawables/DrawableHitObject.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 2a3e3716e5..a48627208d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -10,6 +10,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Rulesets.Judgements; @@ -476,12 +477,21 @@ namespace osu.Game.Rulesets.Objects.Drawables throw new InvalidOperationException($"{GetType().ReadableName()} applied a {nameof(JudgementResult)} but did not update {nameof(JudgementResult.Type)}."); // Some (especially older) rulesets use scorable judgements instead of the newer ignorehit/ignoremiss judgements. + // Can be removed 20210328 if (Result.Judgement.MaxResult == HitResult.IgnoreHit) { + HitResult originalType = Result.Type; + if (Result.Type == HitResult.Miss) Result.Type = HitResult.IgnoreMiss; else if (Result.Type >= HitResult.Meh && Result.Type <= HitResult.Perfect) Result.Type = HitResult.IgnoreHit; + + if (Result.Type != originalType) + { + Logger.Log($"{GetType().ReadableName()} applied an invalid hit result ({originalType}) when {nameof(HitResult.IgnoreMiss)} or {nameof(HitResult.IgnoreHit)} is expected.\n" + + $"This has been automatically adjusted to {Result.Type}, and support will be removed from 2020-03-28 onwards.", level: LogLevel.Important); + } } if (!Result.Type.IsValidHitResult(Result.Judgement.MinResult, Result.Judgement.MaxResult)) From 61e62929eeccb402000af318ba438c0c78e2e6ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 12:51:33 +0900 Subject: [PATCH 081/164] Apply changes in line with framework event logic update --- osu.Game.Tournament/Components/DateTextBox.cs | 2 +- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 3 ++- osu.Game/Overlays/ChatOverlay.cs | 3 ++- osu.Game/Overlays/Music/PlaylistOverlay.cs | 2 +- osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs | 3 ++- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index aee5241e35..a1b5ac38ea 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tournament.Components { base.Bindable = new Bindable(); - ((OsuTextBox)Control).OnCommit = (sender, newText) => + ((OsuTextBox)Control).OnCommit += (sender, newText) => { try { diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index f8810c778f..8b0caddbc6 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -59,12 +59,13 @@ namespace osu.Game.Online.Chat RelativeSizeAxes = Axes.X, Height = textbox_height, PlaceholderText = "type your message", - OnCommit = postMessage, ReleaseFocusOnCommit = false, HoldFocus = true, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }); + + textbox.OnCommit += postMessage; } Channel.BindValueChanged(channelChanged); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index c53eccf78b..8bc7e21047 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -146,7 +146,6 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Height = 1, PlaceholderText = "type your message", - OnCommit = postMessage, ReleaseFocusOnCommit = false, HoldFocus = true, } @@ -186,6 +185,8 @@ namespace osu.Game.Overlays }, }; + textbox.OnCommit += postMessage; + ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; ChannelSelectionOverlay.State.ValueChanged += state => diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 050e687dfb..b8d04eab4e 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music }, }; - filter.Search.OnCommit = (sender, newText) => + filter.Search.OnCommit += (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 34e5da4ef4..f96e204f62 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -236,7 +236,6 @@ namespace osu.Game.Overlays.Settings.Sections.General PlaceholderText = "password", RelativeSizeAxes = Axes.X, TabbableContentContainer = this, - OnCommit = (sender, newText) => performLogin() }, new SettingsCheckbox { @@ -276,6 +275,8 @@ namespace osu.Game.Overlays.Settings.Sections.General } } }; + + password.OnCommit += (sender, newText) => performLogin(); } public override bool AcceptsFocus => true; From e0a0902a15f9e1dac34b8795035c2ef48e47dbec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 13:06:24 +0900 Subject: [PATCH 082/164] Ensure textbox always reverts to sane state on out-of-range failures --- .../Edit/Timing/SliderWithTextBoxInput.cs | 7 +++++-- osu.Game/Screens/Edit/Timing/TimingSection.cs | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 07b914c506..14023b0c35 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -54,9 +54,12 @@ namespace osu.Game.Screens.Edit.Timing } catch { - // will restore the previous text value on failure. - Current.TriggerChange(); + // TriggerChange below will restore the previous text value on failure. } + + // This is run regardless of parsing success as the parsed number may not actually trigger a change + // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. + Current.TriggerChange(); }; Current.BindValueChanged(val => diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index cc79dd2acc..0202441537 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -65,18 +65,19 @@ namespace osu.Game.Screens.Edit.Timing { if (!isNew) return; - if (double.TryParse(Current.Value, out double doubleVal)) + try { - try - { + if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0) beatLengthBindable.Value = beatLengthToBpm(doubleVal); - } - catch - { - // will restore the previous text value on failure. - beatLengthBindable.TriggerChange(); - } } + catch + { + // TriggerChange below will restore the previous text value on failure. + } + + // This is run regardless of parsing success as the parsed number may not actually trigger a change + // due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. + beatLengthBindable.TriggerChange(); }; beatLengthBindable.BindValueChanged(val => From 482c23901b4b9aed6f62be5d2fbe719874e72ff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 16:54:56 +0900 Subject: [PATCH 083/164] Check RequestedPlaying state before allowing scheduled resume of looped sample --- osu.Game/Skinning/PausableSkinnableSound.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index d080e2ccd9..9819574b1d 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -41,8 +41,14 @@ namespace osu.Game.Skinning // it's not easy to know if a sample has finished playing (to end). // to keep things simple only resume playing looping samples. else if (Looping) + { // schedule so we don't start playing a sample which is no longer alive. - Schedule(base.Play); + Schedule(() => + { + if (RequestedPlaying) + base.Play(); + }); + } } }); } From 538973e3942ea8c589e5e98f35890605cb69f5b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:06:05 +0900 Subject: [PATCH 084/164] Use float methods for math operations --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 6b4f13db35..daf4a0102b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -183,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit point.Y -= origin.Y; Vector2 ret; - ret.X = (float)(point.X * Math.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Sin(angle / 180f * Math.PI)); - ret.Y = (float)(point.X * -Math.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * Math.Cos(angle / 180f * Math.PI)); + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(angle / 180f * MathF.PI); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(angle / 180f * MathF.PI); ret.X += origin.X; ret.Y += origin.Y; From b6dc8bb2d3f16fa41934187fe9957865db1d2f20 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:10:05 +0900 Subject: [PATCH 085/164] Fix remaining manual degree-to-radian conversions --- osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index daf4a0102b..a0f70ce408 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -183,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Edit point.Y -= origin.Y; Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(angle / 180f * MathF.PI); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(angle / 180f * MathF.PI); + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); ret.X += origin.X; ret.Y += origin.Y; From 0d03084cdc03c849e25320913ae2cff97bdda723 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:19:35 +0900 Subject: [PATCH 086/164] Move control point display to the base timeline class We want them to display on all screens with a timeline as they are quite useful in all cases. --- .../Compose/Components/Timeline/Timeline.cs | 28 ++++++++++++++----- .../Components/Timeline/TimelineArea.cs | 17 ++++++++--- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 ---- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index ed3d328330..a93ad9ac0d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline @@ -21,6 +22,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public class Timeline : ZoomableScrollContainer, IPositionSnapProvider { public readonly Bindable WaveformVisible = new Bindable(); + + public readonly Bindable ControlPointsVisible = new Bindable(); + public readonly IBindable Beatmap = new Bindable(); [Resolved] @@ -56,24 +60,34 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } private WaveformGraph waveform; + private ControlPointPart controlPoints; [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours) { - Add(waveform = new WaveformGraph + AddRange(new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), - LowColour = colours.BlueLighter, - MidColour = colours.BlueDark, - HighColour = colours.BlueDarker, - Depth = float.MaxValue + waveform = new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Blue.Opacity(0.2f), + LowColour = colours.BlueLighter, + MidColour = colours.BlueDark, + HighColour = colours.BlueDarker, + Depth = float.MaxValue + }, + controlPoints = new ControlPointPart + { + RelativeSizeAxes = Axes.Both + }, + new TimelineTickDisplay(), }); // We don't want the centre marker to scroll AddInternal(new CentreMarker { Depth = float.MaxValue }); WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + ControlPointsVisible.ValueChanged += visible => controlPoints.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); Beatmap.BindTo(beatmap); Beatmap.BindValueChanged(b => diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index d870eb5279..1d2d46517b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline CornerRadius = 5; OsuCheckbox waveformCheckbox; + OsuCheckbox controlPointsCheckbox; InternalChildren = new Drawable[] { @@ -57,12 +58,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.CentreLeft, AutoSizeAxes = Axes.Y, Width = 160, - Padding = new MarginPadding { Horizontal = 15 }, + Padding = new MarginPadding { Horizontal = 10 }, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 4), Children = new[] { - waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" } + waveformCheckbox = new OsuCheckbox + { + LabelText = "Waveform", + Current = { Value = true }, + }, + controlPointsCheckbox = new OsuCheckbox + { + LabelText = "Control Points", + Current = { Value = true }, + } } } } @@ -119,9 +129,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }; - waveformCheckbox.Current.Value = true; - Timeline.WaveformVisible.BindTo(waveformCheckbox.Current); + Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); } private void changeZoom(float change) => Timeline.Zoom += change; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0a0cfe193d..269874fea8 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -12,7 +12,6 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -31,11 +30,6 @@ namespace osu.Game.Screens.Edit.Timing { } - protected override Drawable CreateTimelineContent() => new ControlPointPart - { - RelativeSizeAxes = Axes.Both, - }; - protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, From b654396a4cb89def74d240b795cc73dbf2602534 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 17:40:31 +0900 Subject: [PATCH 087/164] Move ticks display to timeline --- osu.Game/Screens/Edit/EditorScreenWithTimeline.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs index d6d782e70c..b9457f422a 100644 --- a/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs +++ b/osu.Game/Screens/Edit/EditorScreenWithTimeline.cs @@ -112,7 +112,6 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Children = new[] { - new TimelineTickDisplay(), CreateTimelineContent(), } }, t => From 00a19b4879954b5ab4f143f417eb477763f4e5f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:14:10 +0900 Subject: [PATCH 088/164] Also add toggle for ticks display --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 6 +++++- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a93ad9ac0d..e6b0dd715a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -25,6 +25,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public readonly Bindable ControlPointsVisible = new Bindable(); + public readonly Bindable TicksVisible = new Bindable(); + public readonly IBindable Beatmap = new Bindable(); [Resolved] @@ -61,6 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private WaveformGraph waveform; private ControlPointPart controlPoints; + private TimelineTickDisplay ticks; [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours) @@ -80,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Both }, - new TimelineTickDisplay(), + ticks = new TimelineTickDisplay(), }); // We don't want the centre marker to scroll @@ -88,6 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); ControlPointsVisible.ValueChanged += visible => controlPoints.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + TicksVisible.ValueChanged += visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); Beatmap.BindTo(beatmap); Beatmap.BindValueChanged(b => diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 1d2d46517b..0ec48e04c6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -26,6 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OsuCheckbox waveformCheckbox; OsuCheckbox controlPointsCheckbox; + OsuCheckbox ticksCheckbox; InternalChildren = new Drawable[] { @@ -72,6 +73,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { LabelText = "Control Points", Current = { Value = true }, + }, + ticksCheckbox = new OsuCheckbox + { + LabelText = "Ticks", + Current = { Value = true }, } } } @@ -131,6 +137,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.WaveformVisible.BindTo(waveformCheckbox.Current); Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); + Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } private void changeZoom(float change) => Timeline.Zoom += change; From ffc1e9c35881288b24a2e11b5a405190bb14e860 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:23:38 +0900 Subject: [PATCH 089/164] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index afc5d4ec52..78ceaa8616 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5fa1685d9b..3a839ac1a4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 60708a39e2..48c91f0d53 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 70d475be1fec41d3e565fb38cd1e1f768c8525a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Oct 2020 18:54:59 +0900 Subject: [PATCH 090/164] Fix elements appearing in front of hitobjects --- .../Compose/Components/Timeline/Timeline.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index e6b0dd715a..3e54813a14 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -70,20 +71,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { AddRange(new Drawable[] { - waveform = new WaveformGraph + new Container { RelativeSizeAxes = Axes.Both, - Colour = colours.Blue.Opacity(0.2f), - LowColour = colours.BlueLighter, - MidColour = colours.BlueDark, - HighColour = colours.BlueDarker, - Depth = float.MaxValue + Depth = float.MaxValue, + Children = new Drawable[] + { + waveform = new WaveformGraph + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Blue.Opacity(0.2f), + LowColour = colours.BlueLighter, + MidColour = colours.BlueDark, + HighColour = colours.BlueDarker, + }, + controlPoints = new ControlPointPart + { + RelativeSizeAxes = Axes.Both + }, + ticks = new TimelineTickDisplay(), + } }, - controlPoints = new ControlPointPart - { - RelativeSizeAxes = Axes.Both - }, - ticks = new TimelineTickDisplay(), }); // We don't want the centre marker to scroll From 7e5ecd84bc39dabad82b32e926e3a73ceee4ae46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20H=C3=BCbner?= Date: Thu, 1 Oct 2020 12:41:44 +0200 Subject: [PATCH 091/164] Add braces to clear up operator precedence --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index 63cd48676e..a5f20378fe 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerBlink = !source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value ?? true; + spinnerBlink = !(source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value) ?? true; drawableSpinner = (DrawableSpinner)drawableObject; From 3e6af7ce43975aea05208ef52654038a7984360a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:09:09 +0900 Subject: [PATCH 092/164] Refactor for readability --- osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index a5f20378fe..c952500bbf 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load(ISkinSource source, DrawableHitObject drawableObject) { - spinnerBlink = !(source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value) ?? true; + spinnerBlink = source.GetConfig(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true; drawableSpinner = (DrawableSpinner)drawableObject; From ba76089219cd9489cf10b160898cafea7414192c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:24:32 +0900 Subject: [PATCH 093/164] Fix spinner flashing yellow glow before completion --- .../Objects/Drawables/Pieces/DefaultSpinnerDisc.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 2862fe49bd..587bd415ee 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces drawableSpinner.RotationTracker.Complete.BindValueChanged(complete => updateComplete(complete.NewValue, 200)); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } protected override void Update() @@ -124,6 +126,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { + if (!(drawableHitObject is DrawableSpinner)) + return; + centre.ScaleTo(0); mainContainer.ScaleTo(0); From 6d3f4c8699f713ab5146401254647c9aab421f6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 20:38:47 +0900 Subject: [PATCH 094/164] Fix a few more similar cases --- osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs | 5 +++++ osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index bcb2af8e3e..1dfc9c0772 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -72,10 +72,15 @@ namespace osu.Game.Rulesets.Osu.Skinning this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { + if (!(drawableHitObject is DrawableSpinner)) + return; + var spinner = (Spinner)drawableSpinner.HitObject; using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index a45d91801d..c498179eef 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -86,10 +86,15 @@ namespace osu.Game.Rulesets.Osu.Skinning this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; + + updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state) { + if (!(drawableHitObject is DrawableSpinner)) + return; + var spinner = drawableSpinner.HitObject; using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) From d7f9b8045cc99a153342527217246953c146401f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:33:54 +0900 Subject: [PATCH 095/164] Safeguard againts multiple ApplyResult() invocations --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f159d28eed..abfe7eb58c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -469,6 +469,9 @@ namespace osu.Game.Rulesets.Objects.Drawables /// The callback that applies changes to the . protected void ApplyResult(Action application) { + if (Result.HasResult) + throw new InvalidOperationException($"Cannot apply result on a hitobject that already has a result."); + application?.Invoke(Result); if (!Result.HasResult) From 042c39ae1b04f3653cb068acb2089f01213a3aed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:48:45 +0900 Subject: [PATCH 096/164] Remove redundant string interpolation --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index abfe7eb58c..a2d222d0a8 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -470,7 +470,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected void ApplyResult(Action application) { if (Result.HasResult) - throw new InvalidOperationException($"Cannot apply result on a hitobject that already has a result."); + throw new InvalidOperationException("Cannot apply result on a hitobject that already has a result."); application?.Invoke(Result); From ab33434a8a80f3c50228e52e7e7f643a3f71e40c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 1 Oct 2020 21:54:43 +0900 Subject: [PATCH 097/164] Reword xmldocs to better describe nested events --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index f159d28eed..24ee3f629d 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -51,12 +51,12 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool PropagateNonPositionalInputSubTree => HandleUserInput; /// - /// Invoked when a has been applied by this or a nested . + /// Invoked by this or a nested after a has been applied. /// public event Action OnNewResult; /// - /// Invoked when a is being reverted by this or a nested . + /// Invoked by this or a nested prior to a being reverted. /// public event Action OnRevertResult; @@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Objects.Drawables #region State / Transform Management /// - /// Bind to apply a custom state which can override the default implementation. + /// Invoked by this or a nested to apply a custom state that can override the default implementation. /// public event Action ApplyCustomUpdateState; From c72dbf1ba0e84ff3d19c2eb780d292ad1c4d56cf Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2020 17:15:49 +0000 Subject: [PATCH 098/164] Bump ppy.osu.Framework.NativeLibs from 2020.213.0 to 2020.923.0 Bumps [ppy.osu.Framework.NativeLibs](https://github.com/ppy/osu-framework) from 2020.213.0 to 2020.923.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2020.213.0...2020.923.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 48c91f0d53..31f1af135d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -85,6 +85,6 @@ - + From 9e52f9c8582ec697d8ba3658a035747e04336697 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 1 Oct 2020 23:23:28 +0300 Subject: [PATCH 099/164] Consider cursor size in trail interval --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 9bcb3abc63..546bb3f233 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -15,6 +16,7 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Layout; using osu.Framework.Timing; +using osu.Game.Configuration; using osuTK; using osuTK.Graphics; using osuTK.Graphics.ES30; @@ -28,6 +30,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private int currentIndex; private IShader shader; + private Bindable cursorSize; private double timeOffset; private float time; @@ -48,9 +51,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(ShaderManager shaders) + private void load(ShaderManager shaders, OsuConfigManager config) { shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); + cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize); } protected override void LoadComplete() @@ -147,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f; + float interval = partSize.X / 2.5f / cursorSize.Value; for (float d = interval; d < distance; d += interval) { From abf1afd3f125ac970ab1924c7d956629f5477e10 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 1 Oct 2020 23:27:57 +0300 Subject: [PATCH 100/164] Do not decrease density --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 546bb3f233..8a1dc9b8cb 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f / cursorSize.Value; + float interval = partSize.X / 2.5f / Math.Max(cursorSize.Value, 1); for (float d = interval; d < distance; d += interval) { From fa1903cd03c4e5f4efa2e796379ee041f1772ac8 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Thu, 1 Oct 2020 23:41:24 +0300 Subject: [PATCH 101/164] Get bound copy instead --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 8a1dc9b8cb..fb8a850223 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private void load(ShaderManager shaders, OsuConfigManager config) { shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); - cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); } protected override void LoadComplete() From 50722cc754f8cfc20042c11e2a27da2ffb5cdd7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 14:48:56 +0900 Subject: [PATCH 102/164] Update slider test scene sliders to fit better --- .../TestSceneSlider.cs | 74 ++++++++++--------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 6a689a1f80..c79cae2fe5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, + StartTime = Time.Current + time_offset, Position = new Vector2(239, 176), Path = new SliderPath(PathType.PerfectCurve, new[] { @@ -185,22 +185,26 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable testSlowSpeed() => createSlider(speedMultiplier: 0.5); - private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 0.5); + private Drawable testShortSlowSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 0.5); private Drawable testHighSpeed(int repeats = 0) => createSlider(repeats: repeats, speedMultiplier: 15); - private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); + private Drawable testShortHighSpeed(int repeats = 0) => createSlider(distance: max_length / 4, repeats: repeats, speedMultiplier: 15); - private Drawable createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) + private const double time_offset = 1500; + + private const float max_length = 200; + + private Drawable createSlider(float circleSize = 2, float distance = max_length, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-(distance / 2), 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(0, -(distance / 2)), Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, - new Vector2(distance, 0), + new Vector2(0, distance), }, distance), RepeatCount = repeats, StackHeight = stackHeight @@ -213,14 +217,14 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 2, 0), Path = new SliderPath(PathType.PerfectCurve, new[] { Vector2.Zero, - new Vector2(200, 200), - new Vector2(400, 0) - }, 600), + new Vector2(max_length / 2, max_length / 2), + new Vector2(max_length, 0) + }, max_length * 1.5f), RepeatCount = repeats, }; @@ -233,16 +237,16 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 2, 0), Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(150, 75), - new Vector2(200, 0), - new Vector2(300, -200), - new Vector2(400, 0), - new Vector2(430, 0) + new Vector2(max_length * 0.375f, max_length * 0.18f), + new Vector2(max_length / 2, 0), + new Vector2(max_length * 0.75f, -max_length / 2), + new Vector2(max_length * 0.95f, 0), + new Vector2(max_length, 0) }), RepeatCount = repeats, }; @@ -256,15 +260,15 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-200, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 2, 0), Path = new SliderPath(PathType.Bezier, new[] { Vector2.Zero, - new Vector2(150, 75), - new Vector2(200, 100), - new Vector2(300, -200), - new Vector2(430, 0) + new Vector2(max_length * 0.375f, max_length * 0.18f), + new Vector2(max_length / 2, max_length / 4), + new Vector2(max_length * 0.75f, -max_length / 2), + new Vector2(max_length, 0) }), RepeatCount = repeats, }; @@ -278,16 +282,16 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - StartTime = Time.Current + 1000, + StartTime = Time.Current + time_offset, Position = new Vector2(0, 0), Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(-200, 0), + new Vector2(-max_length / 2, 0), new Vector2(0, 0), - new Vector2(0, -200), - new Vector2(-200, -200), - new Vector2(0, -200) + new Vector2(0, -max_length / 2), + new Vector2(-max_length / 2, -max_length / 2), + new Vector2(0, -max_length / 2) }), RepeatCount = repeats, }; @@ -305,14 +309,14 @@ namespace osu.Game.Rulesets.Osu.Tests var slider = new Slider { - StartTime = Time.Current + 1000, - Position = new Vector2(-100, 0), + StartTime = Time.Current + time_offset, + Position = new Vector2(-max_length / 4, 0), Path = new SliderPath(PathType.Catmull, new[] { Vector2.Zero, - new Vector2(50, -50), - new Vector2(150, 50), - new Vector2(200, 0) + new Vector2(max_length * 0.125f, max_length * 0.125f), + new Vector2(max_length * 0.375f, max_length * 0.125f), + new Vector2(max_length / 2, 0) }), RepeatCount = repeats, NodeSamples = repeatSamples From 78bf58f4f8c469f4dce04f7b3db7c31174a04cd3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:38:13 +0900 Subject: [PATCH 103/164] Add metrics skin elements for sliderendcircle --- .../metrics-skin/sliderendcircle@2x.png | Bin 0 -> 18105 bytes .../metrics-skin/sliderendcircleoverlay@2x.png | Bin 0 -> 45734 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircle@2x.png create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircleoverlay@2x.png diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/sliderendcircle@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c6c3771593701a825f5cf61c8e05be66bd30699f GIT binary patch literal 18105 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 literal 0 HcmV?d00001 From fce3eacd7de3254ce75619efaa2d15d59d564623 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:38:48 +0900 Subject: [PATCH 104/164] Move tail circle to display beneath ticks etc. --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9abcef83c4..e77bca1e20 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables InternalChildren = new Drawable[] { Body = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderBody), _ => new DefaultSliderBody(), confineMode: ConfineMode.NoScaling), + tailContainer = new Container { RelativeSizeAxes = Axes.Both }, tickContainer = new Container { RelativeSizeAxes = Axes.Both }, repeatContainer = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) @@ -63,7 +64,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Alpha = 0 }, headContainer = new Container { RelativeSizeAxes = Axes.Both }, - tailContainer = new Container { RelativeSizeAxes = Axes.Both }, }; } From fc7f3173e19aa8d47ebba85490425eb9e434407c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:40:24 +0900 Subject: [PATCH 105/164] Add the ability to use LegacyMainCirclePiece with no combo number displayed --- .../Skinning/LegacyMainCirclePiece.cs | 38 ++++++++++++------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs index d15a0a3203..f051cbfa3b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs @@ -21,10 +21,12 @@ namespace osu.Game.Rulesets.Osu.Skinning public class LegacyMainCirclePiece : CompositeDrawable { private readonly string priorityLookup; + private readonly bool hasNumber; - public LegacyMainCirclePiece(string priorityLookup = null) + public LegacyMainCirclePiece(string priorityLookup = null, bool hasNumber = true) { this.priorityLookup = priorityLookup; + this.hasNumber = hasNumber; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); } @@ -70,7 +72,11 @@ namespace osu.Game.Rulesets.Osu.Skinning } } }, - hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText + }; + + if (hasNumber) + { + AddInternal(hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, @@ -78,8 +84,8 @@ namespace osu.Game.Rulesets.Osu.Skinning { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }, - }; + }); + } bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; @@ -107,7 +113,8 @@ namespace osu.Game.Rulesets.Osu.Skinning state.BindValueChanged(updateState, true); accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true); - indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); + if (hasNumber) + indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true); } private void updateState(ValueChangedEvent state) @@ -120,16 +127,19 @@ namespace osu.Game.Rulesets.Osu.Skinning circleSprites.FadeOut(legacy_fade_duration, Easing.Out); circleSprites.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; - - if (legacyVersion >= 2.0m) - // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. - hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out); - else + if (hasNumber) { - // old skins scale and fade it normally along other pieces. - hitCircleText.FadeOut(legacy_fade_duration, Easing.Out); - hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + var legacyVersion = skin.GetConfig(LegacySetting.Version)?.Value; + + if (legacyVersion >= 2.0m) + // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. + hitCircleText.FadeOut(legacy_fade_duration / 4, Easing.Out); + else + { + // old skins scale and fade it normally along other pieces. + hitCircleText.FadeOut(legacy_fade_duration, Easing.Out); + hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + } } break; From 5d2a8ec7640fff9ff189f9adca1e9e5c381d29c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 13:41:22 +0900 Subject: [PATCH 106/164] Add final sliderendcircle display support --- .../Objects/Drawables/DrawableSliderRepeat.cs | 25 ++++-- .../Objects/Drawables/DrawableSliderTail.cs | 79 ++++++++++++++++--- .../{SliderCircle.cs => SliderEndCircle.cs} | 2 +- osu.Game.Rulesets.Osu/OsuSkinComponents.cs | 1 + .../Skinning/OsuLegacySkinTransformer.cs | 6 ++ 5 files changed, 94 insertions(+), 19 deletions(-) rename osu.Game.Rulesets.Osu/Objects/{SliderCircle.cs => SliderEndCircle.cs} (82%) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index f65077685f..9d775de7df 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -34,7 +36,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Origin = Anchor.Centre; - InternalChild = scaleContainer = new ReverseArrowPiece(); + InternalChild = scaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + // no default for this; only visible in legacy skins. + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), + arrow = new ReverseArrowPiece(), + } + }; } private readonly IBindable scaleBindable = new BindableFloat(); @@ -85,6 +98,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private bool hasRotation; + private readonly ReverseArrowPiece arrow; + public void UpdateSnakingPosition(Vector2 start, Vector2 end) { // When the repeat is hit, the arrow should fade out on spot rather than following the slider @@ -114,18 +129,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } float aimRotation = MathUtils.RadiansToDegrees(MathF.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); - while (Math.Abs(aimRotation - Rotation) > 180) - aimRotation += aimRotation < Rotation ? 360 : -360; + while (Math.Abs(aimRotation - arrow.Rotation) > 180) + aimRotation += aimRotation < arrow.Rotation ? 360 : -360; if (!hasRotation) { - Rotation = aimRotation; + arrow.Rotation = aimRotation; hasRotation = true; } else { // If we're already snaking, interpolate to smooth out sharp curves (linear sliders, mainly). - Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), Rotation, aimRotation, 0, 50, Easing.OutQuint); + arrow.Rotation = Interpolation.ValueAt(Math.Clamp(Clock.ElapsedFrameTime, 0, 100), arrow.Rotation, aimRotation, 0, 50, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 0939e2847a..3751ff0975 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -1,13 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking { private readonly Slider slider; @@ -18,28 +23,73 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public bool Tracking { get; set; } - private readonly IBindable positionBindable = new Bindable(); - private readonly IBindable pathVersion = new Bindable(); + private readonly IBindable scaleBindable = new BindableFloat(); + + private readonly SkinnableDrawable circlePiece; + + private readonly Container scaleContainer; public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) : base(hitCircle) { this.slider = slider; - Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - FillMode = FillMode.Fit; + Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); - AlwaysPresent = true; + InternalChildren = new Drawable[] + { + scaleContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + // no default for this; only visible in legacy skins. + circlePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()) + } + }, + }; + } - positionBindable.BindTo(hitCircle.PositionBindable); - pathVersion.BindTo(slider.Path.Version); + [BackgroundDependencyLoader] + private void load() + { + scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); + scaleBindable.BindTo(HitObject.ScaleBindable); + } - positionBindable.BindValueChanged(_ => updatePosition()); - pathVersion.BindValueChanged(_ => updatePosition(), true); + protected override void UpdateInitialTransforms() + { + base.UpdateInitialTransforms(); - // TODO: This has no drawable content. Support for skins should be added. + circlePiece.FadeInFromZero(HitObject.TimeFadeIn); + } + + protected override void UpdateStateTransforms(ArmedState state) + { + base.UpdateStateTransforms(state); + + Debug.Assert(HitObject.HitWindows != null); + + switch (state) + { + case ArmedState.Idle: + this.Delay(HitObject.TimePreempt).FadeOut(500); + + Expire(true); + break; + + case ArmedState.Miss: + this.FadeOut(100); + break; + + case ArmedState.Hit: + // todo: temporary / arbitrary + this.Delay(800).FadeOut(); + break; + } } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -48,6 +98,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } - private void updatePosition() => Position = HitObject.Position - slider.Position; + public void UpdateSnakingPosition(Vector2 start, Vector2 end) + { + Position = end; + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs similarity index 82% rename from osu.Game.Rulesets.Osu/Objects/SliderCircle.cs rename to osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index 151902a752..d9ae520f5c 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Osu.Objects { - public class SliderCircle : HitCircle + public class SliderEndCircle : HitCircle { } } diff --git a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs index 5468764692..2883f0c187 100644 --- a/osu.Game.Rulesets.Osu/OsuSkinComponents.cs +++ b/osu.Game.Rulesets.Osu/OsuSkinComponents.cs @@ -14,6 +14,7 @@ namespace osu.Game.Rulesets.Osu ReverseArrow, HitCircleText, SliderHeadHitCircle, + SliderTailHitCircle, SliderFollowCircle, SliderBall, SliderBody, diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index 851a8d56c9..78bc26eff7 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; + case OsuSkinComponents.SliderTailHitCircle: + if (hasHitCircle.Value) + return new LegacyMainCirclePiece("sliderendcircle", false); + + return null; + case OsuSkinComponents.SliderHeadHitCircle: if (hasHitCircle.Value) return new LegacyMainCirclePiece("sliderstartcircle"); From 2427ae43da2284d31e5c2b26662f6df93c0739ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 14:20:55 +0900 Subject: [PATCH 107/164] Share fade in logic with repeats --- .../Objects/Drawables/DrawableSliderTail.cs | 14 +++++----- osu.Game.Rulesets.Osu/Objects/Slider.cs | 4 ++- .../Objects/SliderEndCircle.cs | 27 ++++++++++++++++++- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 23 +--------------- .../Objects/SliderTailCircle.cs | 13 +-------- 5 files changed, 37 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 3751ff0975..f5bcecccdf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking, ITrackSnaking { - private readonly Slider slider; + private readonly SliderTailCircle tailCircle; ///

/// The judgement text is provided by the . @@ -29,10 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Container scaleContainer; - public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) - : base(hitCircle) + public DrawableSliderTail(Slider slider, SliderTailCircle tailCircle) + : base(tailCircle) { - this.slider = slider; + this.tailCircle = tailCircle; Origin = Anchor.Centre; Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2); @@ -98,9 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApplyResult(r => r.Type = Tracking ? r.Judgement.MaxResult : r.Judgement.MinResult); } - public void UpdateSnakingPosition(Vector2 start, Vector2 end) - { - Position = end; - } + public void UpdateSnakingPosition(Vector2 start, Vector2 end) => + Position = tailCircle.RepeatIndex % 2 == 0 ? end : start; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 51f6a44a87..9cc3f17c55 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -174,8 +174,10 @@ namespace osu.Game.Rulesets.Osu.Objects // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. // if this is to change, we should revisit this. - AddNested(TailCircle = new SliderTailCircle(this) + AddNested(TailCircle = new SliderTailCircle { + RepeatIndex = e.SpanIndex, + SpanDuration = SpanDuration, StartTime = e.Time, Position = EndPosition, StackHeight = StackHeight diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index d9ae520f5c..a34eec0c79 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -1,9 +1,34 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Scoring; + namespace osu.Game.Rulesets.Osu.Objects { - public class SliderEndCircle : HitCircle + /// + /// A hitcircle which is at the end of a slider path (either repeat or final tail). + /// + public abstract class SliderEndCircle : HitCircle { + public int RepeatIndex { get; set; } + public double SpanDuration { get; set; } + + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + base.ApplyDefaultsToSelf(controlPointInfo, difficulty); + + // Out preempt should be one span early to give the user ample warning. + TimePreempt += SpanDuration; + + // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders + // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. + if (RepeatIndex > 0) + TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); + } + + protected override HitWindows CreateHitWindows() => HitWindows.Empty; } } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index b6c58a75d1..6bf0ec0355 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -1,35 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class SliderRepeat : OsuHitObject + public class SliderRepeat : SliderEndCircle { - public int RepeatIndex { get; set; } - public double SpanDuration { get; set; } - - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - - // Out preempt should be one span early to give the user ample warning. - TimePreempt += SpanDuration; - - // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders - // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. - if (RepeatIndex > 0) - TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); - } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderRepeatJudgement(); public class SliderRepeatJudgement : OsuJudgement diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index aff3f38e17..2f1bfdfcc0 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; @@ -13,18 +12,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// Note that this should not be used for timing correctness. /// See usage in for more information. /// - public class SliderTailCircle : SliderCircle + public class SliderTailCircle : SliderEndCircle { - private readonly IBindable pathVersion = new Bindable(); - - public SliderTailCircle(Slider slider) - { - pathVersion.BindTo(slider.Path.Version); - pathVersion.BindValueChanged(_ => Position = slider.EndPosition); - } - - protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderTailJudgement(); public class SliderTailJudgement : OsuJudgement From 2975ea9210a7e329a2ae35dfb7f0ef57a283fd74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 14:37:07 +0900 Subject: [PATCH 108/164] Adjust repeat/tail fade in to match stable closer --- osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index a34eec0c79..e0bbac67fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Scoring; @@ -20,13 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - // Out preempt should be one span early to give the user ample warning. - TimePreempt += SpanDuration; - - // We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders - // we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time. if (RepeatIndex > 0) - TimePreempt = Math.Min(SpanDuration * 2, TimePreempt); + { + // Repeat points after the first span should appear behind the still-visible one. + TimeFadeIn = 0; + + // The next end circle should appear exactly after the previous circle (on the same end) is hit. + TimePreempt = SpanDuration * 2; + } } protected override HitWindows CreateHitWindows() => HitWindows.Empty; From ad4cac13acccaa6a30b81470afa0a4a74f5a166c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:21:52 +0900 Subject: [PATCH 109/164] Add preempt adjustment and fade in first end circle with slider to match stable --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 6 ++---- .../Objects/SliderEndCircle.cs | 20 +++++++++++++++++-- osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs | 5 +++++ .../Objects/SliderTailCircle.cs | 5 +++++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9cc3f17c55..917382eccf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -174,10 +174,9 @@ namespace osu.Game.Rulesets.Osu.Objects // we need to use the LegacyLastTick here for compatibility reasons (difficulty). // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. // if this is to change, we should revisit this. - AddNested(TailCircle = new SliderTailCircle + AddNested(TailCircle = new SliderTailCircle(this) { RepeatIndex = e.SpanIndex, - SpanDuration = SpanDuration, StartTime = e.Time, Position = EndPosition, StackHeight = StackHeight @@ -185,10 +184,9 @@ namespace osu.Game.Rulesets.Osu.Objects break; case SliderEventType.Repeat: - AddNested(new SliderRepeat + AddNested(new SliderRepeat(this) { RepeatIndex = e.SpanIndex, - SpanDuration = SpanDuration, StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, Position = Position + Path.PositionAt(e.PathProgress), StackHeight = StackHeight, diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index e0bbac67fc..a6aed2c00e 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -8,12 +8,20 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { /// - /// A hitcircle which is at the end of a slider path (either repeat or final tail). + /// A hit circle which is at the end of a slider path (either repeat or final tail). /// public abstract class SliderEndCircle : HitCircle { + private readonly Slider slider; + + protected SliderEndCircle(Slider slider) + { + this.slider = slider; + } + public int RepeatIndex { get; set; } - public double SpanDuration { get; set; } + + public double SpanDuration => slider.SpanDuration; protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { @@ -27,6 +35,14 @@ namespace osu.Game.Rulesets.Osu.Objects // The next end circle should appear exactly after the previous circle (on the same end) is hit. TimePreempt = SpanDuration * 2; } + else + { + // taken from osu-stable + const float first_end_circle_preempt_adjust = 2 / 3f; + + // The first end circle should fade in with the slider. + TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust; + } } protected override HitWindows CreateHitWindows() => HitWindows.Empty; diff --git a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs index 6bf0ec0355..cca86361c2 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderRepeat.cs @@ -9,6 +9,11 @@ namespace osu.Game.Rulesets.Osu.Objects { public class SliderRepeat : SliderEndCircle { + public SliderRepeat(Slider slider) + : base(slider) + { + } + public override Judgement CreateJudgement() => new SliderRepeatJudgement(); public class SliderRepeatJudgement : OsuJudgement diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 2f1bfdfcc0..5aa2940e10 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -14,6 +14,11 @@ namespace osu.Game.Rulesets.Osu.Objects ///
public class SliderTailCircle : SliderEndCircle { + public SliderTailCircle(Slider slider) + : base(slider) + { + } + public override Judgement CreateJudgement() => new SliderTailJudgement(); public class SliderTailJudgement : OsuJudgement From d6fe5482d30fe8f5197d18145a47ec8a2644dcca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:28:08 +0900 Subject: [PATCH 110/164] Add failing test showing missing control point removal --- osu.Game.Tests/NonVisual/ControlPointInfoTest.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs index 830e4bc603..90a487c0ac 100644 --- a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -139,6 +139,22 @@ namespace osu.Game.Tests.NonVisual Assert.That(cpi.Groups.Count, Is.EqualTo(0)); } + [Test] + public void TestRemoveGroupAlsoRemovedControlPoints() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + + group.Add(new SampleControlPoint()); + + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); + + cpi.RemoveGroup(group); + + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(0)); + } + [Test] public void TestAddControlPointToGroup() { From f501c88b46f09b6bbcda0ddcc520151a882bdc64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:25:35 +0900 Subject: [PATCH 111/164] Fix individual control points not being removed from group when group is removed --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e7788b75f3..22314f28c7 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -158,6 +158,9 @@ namespace osu.Game.Beatmaps.ControlPoints public void RemoveGroup(ControlPointGroup group) { + foreach (var item in group.ControlPoints.ToArray()) + group.Remove(item); + group.ItemAdded -= groupItemAdded; group.ItemRemoved -= groupItemRemoved; From 959c8730f6c673d1f4ac3970ca43426b4ac97e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:25:48 +0900 Subject: [PATCH 112/164] Add settings section from TimingPointGroups on timing screen --- .../Edit/Timing/ControlPointSettings.cs | 1 + osu.Game/Screens/Edit/Timing/GroupSection.cs | 96 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 osu.Game/Screens/Edit/Timing/GroupSection.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index e1182d9fa4..c40061b97c 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -41,6 +41,7 @@ namespace osu.Game.Screens.Edit.Timing private IReadOnlyList createSections() => new Drawable[] { + new GroupSection(), new TimingSection(), new DifficultySection(), new SampleSection(), diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs new file mode 100644 index 0000000000..2c3c393e3c --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class GroupSection : CompositeDrawable + { + private LabelledTextBox textBox; + + [Resolved] + protected Bindable SelectedGroup { get; private set; } + + [Resolved] + protected IBindable Beatmap { get; private set; } + + [Resolved] + private EditorClock clock { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding(10); + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + textBox = new LabelledTextBox + { + Label = "Time" + }, + new TriangleButton + { + Text = "Use current time", + RelativeSizeAxes = Axes.X, + Action = () => changeSelectedGroupTime(clock.CurrentTime) + } + } + }, + }; + + textBox.OnCommit += (sender, isNew) => + { + if (double.TryParse(sender.Text, out var newTime)) + { + changeSelectedGroupTime(newTime); + } + }; + + SelectedGroup.BindValueChanged(group => + { + if (group.NewValue == null) + { + textBox.Text = string.Empty; + textBox.Current.Disabled = true; + return; + } + + textBox.Current.Disabled = false; + textBox.Text = $"{group.NewValue.Time:n0}"; + }, true); + } + + private void changeSelectedGroupTime(in double time) + { + var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray(); + + Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value); + + foreach (var cp in currentGroupItems) + Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp); + + SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time); + } + } +} From 2698dc513f31d4d2a0a0f75b1615c50cdc106800 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:33:33 +0900 Subject: [PATCH 113/164] Add basic textbox error handling --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 2c3c393e3c..ac9c4be97a 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -61,10 +61,17 @@ namespace osu.Game.Screens.Edit.Timing textBox.OnCommit += (sender, isNew) => { + if (!isNew) + return; + if (double.TryParse(sender.Text, out var newTime)) { changeSelectedGroupTime(newTime); } + else + { + SelectedGroup.TriggerChange(); + } }; SelectedGroup.BindValueChanged(group => From 0cb3926e1d090b7c336b16a5512f31f696d18661 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:44:32 +0900 Subject: [PATCH 114/164] Add event on EditorChangeHandler state change --- .../Editing/EditorChangeHandlerTest.cs | 22 ++++++++++++++++++- osu.Game/Screens/Edit/EditorChangeHandler.cs | 5 +++++ osu.Game/Screens/Edit/IEditorChangeHandler.cs | 6 +++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs index ff2c9fb1a9..b7a41ffd1c 100644 --- a/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs +++ b/osu.Game.Tests/Editing/EditorChangeHandlerTest.cs @@ -12,6 +12,14 @@ namespace osu.Game.Tests.Editing [TestFixture] public class EditorChangeHandlerTest { + private int stateChangedFired; + + [SetUp] + public void SetUp() + { + stateChangedFired = 0; + } + [Test] public void TestSaveRestoreState() { @@ -23,6 +31,8 @@ namespace osu.Game.Tests.Editing addArbitraryChange(beatmap); handler.SaveState(); + Assert.That(stateChangedFired, Is.EqualTo(1)); + Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.False); @@ -30,6 +40,8 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.True); + + Assert.That(stateChangedFired, Is.EqualTo(2)); } [Test] @@ -45,6 +57,7 @@ namespace osu.Game.Tests.Editing Assert.That(handler.CanUndo.Value, Is.True); Assert.That(handler.CanRedo.Value, Is.False); + Assert.That(stateChangedFired, Is.EqualTo(1)); string hash = handler.CurrentStateHash; @@ -52,6 +65,7 @@ namespace osu.Game.Tests.Editing handler.SaveState(); Assert.That(hash, Is.EqualTo(handler.CurrentStateHash)); + Assert.That(stateChangedFired, Is.EqualTo(1)); handler.RestoreState(-1); @@ -60,6 +74,7 @@ namespace osu.Game.Tests.Editing // we should only be able to restore once even though we saved twice. Assert.That(handler.CanUndo.Value, Is.False); Assert.That(handler.CanRedo.Value, Is.True); + Assert.That(stateChangedFired, Is.EqualTo(2)); } [Test] @@ -71,6 +86,8 @@ namespace osu.Game.Tests.Editing for (int i = 0; i < EditorChangeHandler.MAX_SAVED_STATES; i++) { + Assert.That(stateChangedFired, Is.EqualTo(i)); + addArbitraryChange(beatmap); handler.SaveState(); } @@ -114,7 +131,10 @@ namespace osu.Game.Tests.Editing { var beatmap = new EditorBeatmap(new Beatmap()); - return (new EditorChangeHandler(beatmap), beatmap); + var changeHandler = new EditorChangeHandler(beatmap); + + changeHandler.OnStateChange += () => stateChangedFired++; + return (changeHandler, beatmap); } private void addArbitraryChange(EditorBeatmap beatmap) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 617c436ee0..616d0608c0 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -21,6 +21,8 @@ namespace osu.Game.Screens.Edit public readonly Bindable CanUndo = new Bindable(); public readonly Bindable CanRedo = new Bindable(); + public event Action OnStateChange; + private readonly LegacyEditorBeatmapPatcher patcher; private readonly List savedStates = new List(); @@ -109,6 +111,8 @@ namespace osu.Game.Screens.Edit savedStates.Add(newState); currentState = savedStates.Count - 1; + + OnStateChange?.Invoke(); updateBindables(); } } @@ -136,6 +140,7 @@ namespace osu.Game.Screens.Edit isRestoring = false; + OnStateChange?.Invoke(); updateBindables(); } diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index c1328252d4..a23a956e14 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit @@ -10,6 +11,11 @@ namespace osu.Game.Screens.Edit ///
public interface IEditorChangeHandler { + /// + /// Fired whenever a state change occurs. + /// + public event Action OnStateChange; + /// /// Begins a bulk state change event. should be invoked soon after. /// From 501e02db097eab45553f0376caf420457b6cbb2d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 15:44:37 +0900 Subject: [PATCH 115/164] Only regenerate autoplay on editor state change --- osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index 1070b8cbd2..d259a89055 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -40,17 +40,21 @@ namespace osu.Game.Rulesets.Edit Playfield.DisplayJudgements.Value = false; } + [Resolved] + private IEditorChangeHandler changeHandler { get; set; } + protected override void LoadComplete() { base.LoadComplete(); beatmap.HitObjectAdded += addHitObject; - beatmap.HitObjectUpdated += updateReplay; beatmap.HitObjectRemoved += removeHitObject; + + // for now only regenerate replay on a finalised state change, not HitObjectUpdated. + changeHandler.OnStateChange += updateReplay; } - private void updateReplay(HitObject obj = null) => - drawableRuleset.RegenerateAutoplay(); + private void updateReplay() => drawableRuleset.RegenerateAutoplay(); private void addHitObject(HitObject hitObject) { @@ -69,7 +73,7 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); - drawableRuleset.RegenerateAutoplay(); + updateReplay(); } public override bool PropagatePositionalInputSubTree => false; From dde7f706aafae01330c084719124c504b1abc0ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 01:48:41 +0900 Subject: [PATCH 116/164] Avoid rapid triangle repositioning during editor slider placement --- .../Objects/Drawables/Pieces/CirclePiece.cs | 5 +++-- .../Drawables/Pieces/TrianglesPiece.cs | 3 ++- osu.Game/Graphics/Backgrounds/Triangles.cs | 21 ++++++++++++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index aab01f45d4..e95cdc7ee3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -25,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(TextureStore textures, DrawableHitObject drawableHitObject) { InternalChildren = new Drawable[] { @@ -35,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Origin = Anchor.Centre, Texture = textures.Get(@"Gameplay/osu/disc"), }, - new TrianglesPiece + new TrianglesPiece((int)drawableHitObject.HitObject.StartTime) { RelativeSizeAxes = Axes.Both, Blending = BlendingParameters.Additive, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index 0e29a1dcd8..6cdb0d3df3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -11,7 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces protected override bool CreateNewTriangles => false; protected override float SpawnRatio => 0.5f; - public TrianglesPiece() + public TrianglesPiece(int? seed = null) + : base(seed) { TriangleScale = 1.2f; HideAlphaDiscrepancies = false; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index 27027202ce..5b0fa44444 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -86,13 +86,24 @@ namespace osu.Game.Graphics.Backgrounds ///
public float Velocity = 1; + private readonly Random stableRandom; + + private float nextRandom() => (float)(stableRandom?.NextDouble() ?? RNG.NextSingle()); + private readonly SortedList parts = new SortedList(Comparer.Default); private IShader shader; private readonly Texture texture; - public Triangles() + /// + /// Construct a new triangle visualisation. + /// + /// An optional seed to stabilise random positions / attributes. Note that this does not guarantee stable playback when seeking in time. + public Triangles(int? seed = null) { + if (seed != null) + stableRandom = new Random(seed.Value); + texture = Texture.WhitePixel; } @@ -175,8 +186,8 @@ namespace osu.Game.Graphics.Backgrounds { TriangleParticle particle = CreateTriangle(); - particle.Position = new Vector2(RNG.NextSingle(), randomY ? RNG.NextSingle() : 1); - particle.ColourShade = RNG.NextSingle(); + particle.Position = new Vector2(nextRandom(), randomY ? nextRandom() : 1); + particle.ColourShade = nextRandom(); particle.Colour = CreateTriangleShade(particle.ColourShade); return particle; @@ -191,8 +202,8 @@ namespace osu.Game.Graphics.Backgrounds const float std_dev = 0.16f; const float mean = 0.5f; - float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats - float u2 = 1 - RNG.NextSingle(); + float u1 = 1 - nextRandom(); //uniform(0,1] random floats + float u2 = 1 - nextRandom(); float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1) var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2) From fe818a020a52896aa082de261d4dee9d2ec6a37e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 2 Oct 2020 16:17:57 +0900 Subject: [PATCH 117/164] Fix spinners not transforming correctly --- .../Drawables/Pieces/DefaultSpinnerDisc.cs | 74 +++++++++++-------- .../Skinning/LegacyNewStyleSpinner.cs | 15 ++-- .../Skinning/LegacyOldStyleSpinner.cs | 5 +- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs index 587bd415ee..e855317544 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultSpinnerDisc.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private Spinner spinner; + private const float initial_scale = 1.3f; private const float idle_alpha = 0.2f; private const float tracking_alpha = 0.4f; @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces // we are slightly bigger than our parent, to clip the top and bottom of the circle // this should probably be revisited when scaled spinners are a thing. - Scale = new Vector2(1.3f); + Scale = new Vector2(initial_scale); Anchor = Anchor.Centre; Origin = Anchor.Centre; @@ -117,8 +118,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces fill.Alpha = (float)Interpolation.Damp(fill.Alpha, drawableSpinner.RotationTracker.Tracking ? tracking_alpha : idle_alpha, 0.98f, (float)Math.Abs(Clock.ElapsedFrameTime)); } - const float initial_scale = 0.2f; - float targetScale = initial_scale + (1 - initial_scale) * drawableSpinner.Progress; + const float initial_fill_scale = 0.2f; + float targetScale = initial_fill_scale + (1 - initial_fill_scale) * drawableSpinner.Progress; fill.Scale = new Vector2((float)Interpolation.Lerp(fill.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); mainContainer.Rotation = drawableSpinner.RotationTracker.Rotation; @@ -129,41 +130,54 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (!(drawableHitObject is DrawableSpinner)) return; - centre.ScaleTo(0); - mainContainer.ScaleTo(0); - - using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt / 2, true)) + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) { - // constant ambient rotation to give the spinner "spinning" character. - this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); - - centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); - mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + this.ScaleTo(initial_scale); + this.RotateTo(0); using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) { - centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); - mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + // constant ambient rotation to give the spinner "spinning" character. + this.RotateTo((float)(25 * spinner.Duration / 2000), spinner.TimePreempt + spinner.Duration); + } + + using (BeginDelayedSequence(spinner.TimePreempt + spinner.Duration + drawableHitObject.Result.TimeOffset, true)) + { + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(initial_scale * 1.2f, 320, Easing.Out); + this.RotateTo(mainContainer.Rotation + 180, 320); + break; + + case ArmedState.Miss: + this.ScaleTo(initial_scale * 0.8f, 320, Easing.In); + break; + } + } + } + + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + { + centre.ScaleTo(0); + mainContainer.ScaleTo(0); + + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + { + centre.ScaleTo(0.3f, spinner.TimePreempt / 4, Easing.OutQuint); + mainContainer.ScaleTo(0.2f, spinner.TimePreempt / 4, Easing.OutQuint); + + using (BeginDelayedSequence(spinner.TimePreempt / 2, true)) + { + centre.ScaleTo(0.5f, spinner.TimePreempt / 2, Easing.OutQuint); + mainContainer.ScaleTo(1, spinner.TimePreempt / 2, Easing.OutQuint); + } } } // transforms we have from completing the spinner will be rolled back, so reapply immediately. - updateComplete(state == ArmedState.Hit, 0); - - using (BeginDelayedSequence(spinner.Duration, true)) - { - switch (state) - { - case ArmedState.Hit: - this.ScaleTo(Scale * 1.2f, 320, Easing.Out); - this.RotateTo(mainContainer.Rotation + 180, 320); - break; - - case ArmedState.Miss: - this.ScaleTo(Scale * 0.8f, 320, Easing.In); - break; - } - } + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + updateComplete(state == ArmedState.Hit, 0); } private void updateComplete(bool complete, double duration) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs index 1dfc9c0772..56b5571ce1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyNewStyleSpinner.cs @@ -70,9 +70,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } @@ -83,12 +81,19 @@ namespace osu.Game.Rulesets.Osu.Skinning var spinner = (Spinner)drawableSpinner.HitObject; + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + this.FadeOut(); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) this.FadeInFromZero(spinner.TimeFadeIn / 2); - fixedMiddle.FadeColour(Color4.White); - using (BeginAbsoluteSequence(spinner.StartTime, true)) - fixedMiddle.FadeColour(Color4.Red, spinner.Duration); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + { + fixedMiddle.FadeColour(Color4.White); + + using (BeginDelayedSequence(spinner.TimePreempt, true)) + fixedMiddle.FadeColour(Color4.Red, spinner.Duration); + } } protected override void Update() diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs index eba9abda0b..7b0d7acbbc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs @@ -88,9 +88,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { base.LoadComplete(); - this.FadeOut(); drawableSpinner.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(drawableSpinner, drawableSpinner.State.Value); } @@ -101,6 +99,9 @@ namespace osu.Game.Rulesets.Osu.Skinning var spinner = drawableSpinner.HitObject; + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt, true)) + this.FadeOut(); + using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2, true)) this.FadeInFromZero(spinner.TimeFadeIn / 2); } From b7c276093db90227293a4fc8505e3d3aaa46f5cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 16:21:50 +0900 Subject: [PATCH 118/164] Add fallback case when EditorChangeHandler is not present (for tests) --- .../Rulesets/Edit/DrawableEditRulesetWrapper.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index d259a89055..43e5153f24 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Edit Playfield.DisplayJudgements.Value = false; } - [Resolved] + [Resolved(canBeNull: true)] private IEditorChangeHandler changeHandler { get; set; } protected override void LoadComplete() @@ -50,8 +50,15 @@ namespace osu.Game.Rulesets.Edit beatmap.HitObjectAdded += addHitObject; beatmap.HitObjectRemoved += removeHitObject; - // for now only regenerate replay on a finalised state change, not HitObjectUpdated. - changeHandler.OnStateChange += updateReplay; + if (changeHandler != null) + { + // for now only regenerate replay on a finalised state change, not HitObjectUpdated. + changeHandler.OnStateChange += updateReplay; + } + else + { + beatmap.HitObjectUpdated += _ => updateReplay(); + } } private void updateReplay() => drawableRuleset.RegenerateAutoplay(); From fc920a8899478ff4e00ff5be84188386799148f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:32:34 +0900 Subject: [PATCH 119/164] Add change handler logic --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index ac9c4be97a..0cc78315d2 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -27,6 +27,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private EditorClock clock { get; set; } + [Resolved(canBeNull: true)] + private IEditorChangeHandler changeHandler { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -90,6 +93,8 @@ namespace osu.Game.Screens.Edit.Timing private void changeSelectedGroupTime(in double time) { + changeHandler?.BeginChange(); + var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray(); Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value); @@ -98,6 +103,8 @@ namespace osu.Game.Screens.Edit.Timing Beatmap.Value.Beatmap.ControlPointInfo.Add(time, cp); SelectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(time); + + changeHandler?.EndChange(); } } } From 00eed295272b57ae3570fa9ef8e87274e9b1870f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:35:41 +0900 Subject: [PATCH 120/164] Don't update time if it hasn't changed --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index 0cc78315d2..ee19aaface 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -93,6 +93,9 @@ namespace osu.Game.Screens.Edit.Timing private void changeSelectedGroupTime(in double time) { + if (time == SelectedGroup.Value.Time) + return; + changeHandler?.BeginChange(); var currentGroupItems = SelectedGroup.Value.ControlPoints.ToArray(); From 436cc572d3666353d24669846155b6c709f0b4f5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:15:28 +0900 Subject: [PATCH 121/164] Expose ChangeHandler.SaveState via interface --- osu.Game/Screens/Edit/EditorChangeHandler.cs | 3 --- osu.Game/Screens/Edit/IEditorChangeHandler.cs | 6 ++++++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs index 617c436ee0..66331d54c0 100644 --- a/osu.Game/Screens/Edit/EditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs @@ -79,9 +79,6 @@ namespace osu.Game.Screens.Edit SaveState(); } - /// - /// Saves the current state. - /// public void SaveState() { if (bulkChangesStarted > 0) diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index c1328252d4..f95df76907 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -29,5 +29,11 @@ namespace osu.Game.Screens.Edit /// This should be invoked as soon as possible after to cause a state change. /// void EndChange(); + + /// + /// Immediately saves the current state. + /// Note that this will be a no-op if there is a change in progress via . + /// + void SaveState(); } } From c1c5b5da8e703e7fc37b9585c4c63f743d4a7180 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:15:58 +0900 Subject: [PATCH 122/164] Push state change on control point group addition / removal --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 0a0cfe193d..3b3ae949c1 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -87,6 +87,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } + [Resolved(canBeNull: true)] + private IEditorChangeHandler changeHandler { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -146,6 +149,7 @@ namespace osu.Game.Screens.Edit.Timing controlGroups.BindCollectionChanged((sender, args) => { table.ControlGroups = controlGroups; + changeHandler.SaveState(); }, true); } From 98fd661b239dbd189cc7fb36cd1a30b8e20083c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:55:47 +0900 Subject: [PATCH 123/164] Add change handling for timing section --- osu.Game/Screens/Edit/Timing/Section.cs | 3 +++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index 603fb77f31..7a81eeb1a4 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -32,6 +32,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] protected Bindable SelectedGroup { get; private set; } + [Resolved(canBeNull: true)] + protected IEditorChangeHandler ChangeHandler { get; private set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 0202441537..2ab8703cc4 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -37,8 +37,13 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { bpmSlider.Bindable = point.NewValue.BeatLengthBindable; + bpmSlider.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); + bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; + // no need to hook change handler here as it's the same bindable as above + timeSignature.Bindable = point.NewValue.TimeSignatureBindable; + timeSignature.Bindable.BindValueChanged(_ => ChangeHandler?.SaveState()); } } @@ -117,6 +122,8 @@ namespace osu.Game.Screens.Edit.Timing bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); base.Bindable = bpmBindable; + + TransferValueOnCommit = true; } public override Bindable Bindable From 693a4ff474ea957bd1d8bc4276b3d75616904278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:56:30 +0900 Subject: [PATCH 124/164] Add change handling for effects section --- osu.Game/Screens/Edit/Timing/EffectSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 71e7f42713..2f143108a9 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -28,7 +28,10 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { kiai.Current = point.NewValue.KiaiModeBindable; + kiai.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; + omitBarLine.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } From 08faef694bde8b66b7234c231ea58b89a058a951 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:58:22 +0900 Subject: [PATCH 125/164] Add change handling for difficulty section --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 78766d9777..b55d74e3b4 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -28,6 +28,7 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { multiplierSlider.Current = point.NewValue.SpeedMultiplierBindable; + multiplierSlider.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } From 9fc9009dbe1a6bba52686e41413aae20c4804652 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:59:47 +0900 Subject: [PATCH 126/164] Add change handling for sample section --- osu.Game/Screens/Edit/Timing/SampleSection.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index de986e28ca..280e19c99a 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -35,7 +35,10 @@ namespace osu.Game.Screens.Edit.Timing if (point.NewValue != null) { bank.Current = point.NewValue.SampleBankBindable; + bank.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); + volume.Current = point.NewValue.SampleVolumeBindable; + volume.Current.BindValueChanged(_ => ChangeHandler?.SaveState()); } } From 519c3ac2bdb6a23e30f48cac57db9e4f62c858e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 17:59:57 +0900 Subject: [PATCH 127/164] Change SliderWithTextBoxInput to transfer on commit --- osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 14023b0c35..d5afc8978d 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -38,6 +38,7 @@ namespace osu.Game.Screens.Edit.Timing }, slider = new SettingsSlider { + TransferValueOnCommit = true, RelativeSizeAxes = Axes.X, } } From 66f5187e6a26ea480fb777d9f5abef93ce7a4e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:20:59 +0900 Subject: [PATCH 128/164] Remove redundant access permission --- osu.Game/Screens/Edit/IEditorChangeHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/IEditorChangeHandler.cs b/osu.Game/Screens/Edit/IEditorChangeHandler.cs index a23a956e14..1774ec6c04 100644 --- a/osu.Game/Screens/Edit/IEditorChangeHandler.cs +++ b/osu.Game/Screens/Edit/IEditorChangeHandler.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit /// /// Fired whenever a state change occurs. /// - public event Action OnStateChange; + event Action OnStateChange; /// /// Begins a bulk state change event. should be invoked soon after. From 575046e5fdc34bc13797cab78517f337136734c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:21:13 +0900 Subject: [PATCH 129/164] Don't update reply on add/remove (will be automatically handled by change handler events) --- osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs index 43e5153f24..8ed7885101 100644 --- a/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRulesetWrapper.cs @@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Add(drawableObject); drawableRuleset.Playfield.PostProcess(); - - updateReplay(); } private void removeHitObject(HitObject hitObject) @@ -79,8 +77,6 @@ namespace osu.Game.Rulesets.Edit drawableRuleset.Playfield.Remove(drawableObject); drawableRuleset.Playfield.PostProcess(); - - updateReplay(); } public override bool PropagatePositionalInputSubTree => false; From 1a0171fb2dc2a4fea5eaa8c57a86cd74932d4ff9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:18:14 +0900 Subject: [PATCH 130/164] Fix tests specifying steps in their constructors --- osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs | 4 +++- osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs | 3 ++- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 3 ++- osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs | 5 ++--- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs index 95e86de884..9c4c2b3d5b 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneHoldNote.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -13,7 +14,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { public class TestSceneHoldNote : ManiaHitObjectTestScene { - public TestSceneHoldNote() + [Test] + public void TestHoldNote() { AddToggleStep("toggle hitting", v => { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index dd5fd93710..76c1b47cca 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -28,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Tests [TestFixture] public class TestSceneNotes : OsuTestScene { - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestVariousNotes() { Child = new FillFlowContainer { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 37df0d6e37..596bc06c68 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; - public TestSceneHitCircle() + [Test] + public void TestVariousHitCircles() { AddStep("Miss Big Single", () => SetContents(() => testSingle(2))); AddStep("Miss Medium Single", () => SetContents(() => testSingle(5))); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index c79cae2fe5..c9e112f76d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -27,7 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private int depthIndex; - public TestSceneSlider() + [Test] + public void TestVariousSliders() { AddStep("Big Single", () => SetContents(() => testSimpleBig())); AddStep("Medium Single", () => SetContents(() => testSimpleMedium())); diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs index 0f605be8f9..e4c0766844 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneHits.cs @@ -3,7 +3,6 @@ using System; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -30,8 +29,8 @@ namespace osu.Game.Rulesets.Taiko.Tests private readonly Random rng = new Random(1337); - [BackgroundDependencyLoader] - private void load() + [Test] + public void TestVariousHits() { AddStep("Hit", () => addHitJudgement(false)); AddStep("Strong hit", () => addStrongHitJudgement(false)); From 5a6c45e2ff43fa7e4c811a46883d577db715faea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:41:28 +0900 Subject: [PATCH 131/164] Fix hidden mod support for sliderendcircle --- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 31 +++++++++++++++++++ .../Objects/Drawables/DrawableSliderRepeat.cs | 4 ++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 08fd13915d..80e40af717 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -39,6 +39,9 @@ namespace osu.Game.Rulesets.Osu.Mods base.ApplyToDrawableHitObjects(drawables); } + private double lastSliderHeadFadeOutStartTime; + private double lastSliderHeadFadeOutDuration; + protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) { if (!(drawable is DrawableOsuHitObject d)) @@ -54,7 +57,35 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { + case DrawableSliderTail sliderTail: + // use stored values from head circle to achieve same fade sequence. + fadeOutDuration = lastSliderHeadFadeOutDuration; + fadeOutStartTime = lastSliderHeadFadeOutStartTime; + + using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) + sliderTail.FadeOut(fadeOutDuration); + + break; + + case DrawableSliderRepeat sliderRepeat: + // use stored values from head circle to achieve same fade sequence. + fadeOutDuration = lastSliderHeadFadeOutDuration; + fadeOutStartTime = lastSliderHeadFadeOutStartTime; + + using (drawable.BeginAbsoluteSequence(fadeOutStartTime, true)) + // only apply to circle piece – reverse arrow is not affected by hidden. + sliderRepeat.CirclePiece.FadeOut(fadeOutDuration); + + break; + case DrawableHitCircle circle: + + if (circle is DrawableSliderHead) + { + lastSliderHeadFadeOutDuration = fadeOutDuration; + lastSliderHeadFadeOutStartTime = fadeOutStartTime; + } + // we don't want to see the approach circle using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) circle.ApproachCircle.Hide(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 9d775de7df..46d47a8c94 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly Drawable scaleContainer; + public readonly Drawable CirclePiece; + public override bool DisplayResult => false; public DrawableSliderRepeat(SliderRepeat sliderRepeat, DrawableSlider drawableSlider) @@ -44,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new Drawable[] { // no default for this; only visible in legacy skins. - new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), + CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), arrow = new ReverseArrowPiece(), } }; From ed34985fdde5b8bcf86169a1f66219def3e0ac9f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 18:47:11 +0900 Subject: [PATCH 132/164] Add step for mania note construction --- .../TestSceneNotes.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 76c1b47cca..fd8a01766b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -31,22 +32,30 @@ namespace osu.Game.Rulesets.Mania.Tests [Test] public void TestVariousNotes() { - Child = new FillFlowContainer + DrawableNote note1 = null; + DrawableNote note2 = null; + DrawableHoldNote holdNote1 = null; + DrawableHoldNote holdNote2 = null; + + AddStep("create notes", () => { - Clock = new FramedClock(new ManualClock()), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(20), - Children = new[] + Child = new FillFlowContainer { - createNoteDisplay(ScrollingDirection.Down, 1, out var note1), - createNoteDisplay(ScrollingDirection.Up, 2, out var note2), - createHoldNoteDisplay(ScrollingDirection.Down, 1, out var holdNote1), - createHoldNoteDisplay(ScrollingDirection.Up, 2, out var holdNote2), - } - }; + Clock = new FramedClock(new ManualClock()), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20), + Children = new[] + { + createNoteDisplay(ScrollingDirection.Down, 1, out note1), + createNoteDisplay(ScrollingDirection.Up, 2, out note2), + createHoldNoteDisplay(ScrollingDirection.Down, 1, out holdNote1), + createHoldNoteDisplay(ScrollingDirection.Up, 2, out holdNote2), + } + }; + }); AddAssert("note 1 facing downwards", () => verifyAnchors(note1, Anchor.y2)); AddAssert("note 2 facing upwards", () => verifyAnchors(note2, Anchor.y0)); From a3ecc6c5a4bb55553b9a4ab97f1859e3f665ec0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 19:56:24 +0900 Subject: [PATCH 133/164] Remove redundant array type specification --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs index 46d47a8c94..2a88f11f69 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderRepeat.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Children = new Drawable[] + Children = new[] { // no default for this; only visible in legacy skins. CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderTailHitCircle), _ => Empty()), From 75ae9f1b30c47e3802fa7b2170e8f9d4d695cc52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Oct 2020 19:57:14 +0900 Subject: [PATCH 134/164] Remove unused using --- osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index fd8a01766b..6b8f5d5d9d 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -9,7 +9,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; From dab50bff6f28dcddc8c9ed68ed2a3e0be71e9822 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Oct 2020 01:27:42 +0900 Subject: [PATCH 135/164] Protect "use current time" button against crash when no timing point is selected --- osu.Game/Screens/Edit/Timing/GroupSection.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/GroupSection.cs b/osu.Game/Screens/Edit/Timing/GroupSection.cs index ee19aaface..c77d48ef0a 100644 --- a/osu.Game/Screens/Edit/Timing/GroupSection.cs +++ b/osu.Game/Screens/Edit/Timing/GroupSection.cs @@ -18,6 +18,8 @@ namespace osu.Game.Screens.Edit.Timing { private LabelledTextBox textBox; + private TriangleButton button; + [Resolved] protected Bindable SelectedGroup { get; private set; } @@ -52,7 +54,7 @@ namespace osu.Game.Screens.Edit.Timing { Label = "Time" }, - new TriangleButton + button = new TriangleButton { Text = "Use current time", RelativeSizeAxes = Axes.X, @@ -82,18 +84,22 @@ namespace osu.Game.Screens.Edit.Timing if (group.NewValue == null) { textBox.Text = string.Empty; + textBox.Current.Disabled = true; + button.Enabled.Value = false; return; } textBox.Current.Disabled = false; + button.Enabled.Value = true; + textBox.Text = $"{group.NewValue.Time:n0}"; }, true); } private void changeSelectedGroupTime(in double time) { - if (time == SelectedGroup.Value.Time) + if (SelectedGroup.Value == null || time == SelectedGroup.Value.Time) return; changeHandler?.BeginChange(); From 16f331cf6d09601c655234cfa245252a90adbe03 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 2 Oct 2020 19:34:06 +0300 Subject: [PATCH 136/164] Move implementation to LegacyCursorTrail --- osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs | 10 +++++++++- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 10 ++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs index 1885c76fcc..eabf797607 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -15,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.Skinning private bool disjointTrail; private double lastTrailTime; + private IBindable cursorSize; public LegacyCursorTrail() { @@ -22,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning } [BackgroundDependencyLoader] - private void load(ISkinSource skin) + private void load(ISkinSource skin, OsuConfigManager config) { Texture = skin.GetTexture("cursortrail"); disjointTrail = skin.GetTexture("cursormiddle") == null; @@ -32,12 +36,16 @@ namespace osu.Game.Rulesets.Osu.Skinning // stable "magic ratio". see OsuPlayfieldAdjustmentContainer for full explanation. Texture.ScaleAdjust *= 1.6f; } + + cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); } protected override double FadeDuration => disjointTrail ? 150 : 500; protected override bool InterpolateMovements => !disjointTrail; + protected override float IntervalMultiplier => Math.Max(cursorSize.Value, 1); + protected override bool OnMouseMove(MouseMoveEvent e) { if (!disjointTrail) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index fb8a850223..c30615e6e9 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -5,7 +5,6 @@ using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; @@ -16,7 +15,6 @@ using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Layout; using osu.Framework.Timing; -using osu.Game.Configuration; using osuTK; using osuTK.Graphics; using osuTK.Graphics.ES30; @@ -30,7 +28,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly TrailPart[] parts = new TrailPart[max_sprites]; private int currentIndex; private IShader shader; - private Bindable cursorSize; private double timeOffset; private float time; @@ -51,10 +48,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, OsuConfigManager config) + private void load(ShaderManager shaders) { shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); - cursorSize = config.GetBindable(OsuSetting.GameplayCursorSize).GetBoundCopy(); } protected override void LoadComplete() @@ -123,6 +119,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor /// protected virtual bool InterpolateMovements => true; + protected virtual float IntervalMultiplier => 1.0f; + private Vector2? lastPosition; private readonly InputResampler resampler = new InputResampler(); @@ -151,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f / Math.Max(cursorSize.Value, 1); + float interval = partSize.X / 2.5f / IntervalMultiplier; for (float d = interval; d < distance; d += interval) { From 8cd13729eeabb94dccdea6057994007ad4dbeac4 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Fri, 2 Oct 2020 19:34:49 +0300 Subject: [PATCH 137/164] Actually multiply by the multiplier --- osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs | 2 +- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs index eabf797607..e6cd7bc59d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs +++ b/osu.Game.Rulesets.Osu/Skinning/LegacyCursorTrail.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Skinning protected override bool InterpolateMovements => !disjointTrail; - protected override float IntervalMultiplier => Math.Max(cursorSize.Value, 1); + protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1); protected override bool OnMouseMove(MouseMoveEvent e) { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index c30615e6e9..0b30c28b8d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor float distance = diff.Length; Vector2 direction = diff / distance; - float interval = partSize.X / 2.5f / IntervalMultiplier; + float interval = partSize.X / 2.5f * IntervalMultiplier; for (float d = interval; d < distance; d += interval) { From 2b1ef16f89ef4e7d9b1646ee0fe5d183176d49fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Oct 2020 22:57:49 +0200 Subject: [PATCH 138/164] Replace comparison references to HitResult.Miss with IsHit --- osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs | 2 +- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs | 2 +- .../TestSceneMissHitWindowJudgements.cs | 4 ++-- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs | 4 ++-- osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs | 2 +- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs | 2 +- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- osu.Game/Screens/Ranking/Statistics/UnstableRate.cs | 2 +- 15 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs index cc01009dd9..75feb21298 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchComboDisplay.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.UI if (!result.Type.AffectsCombo() || !result.HasResult) return; - if (result.Type == HitResult.Miss) + if (!result.IsHit) { updateCombo(0, null); return; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index ba6cad978d..f6d539c91b 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables endHold(); } - if (Tail.Result.Type == HitResult.Miss) + if (Tail.Judged && !Tail.IsHit) HasBroken = true; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index f3221ffe32..39deba2f57 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests { HitObjects = { new HitCircle { Position = new Vector2(256, 192) } } }, - PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss + PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset < -hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Autoplay = false, Beatmap = beatmap, - PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && Player.Results[0].Type == HitResult.Miss + PassCondition = () => Player.Results.Count > 0 && Player.Results[0].TimeOffset >= hitWindows.WindowFor(HitResult.Meh) && !Player.Results[0].IsHit }); } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index d5c3538c81..844449851f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -314,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.Tests private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult); - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && !judgementResults.First().IsHit; private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index a438dc8be4..6d6bd7fc97 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables var circleResult = (OsuHitCircleJudgementResult)r; // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. - if (result != HitResult.Miss) + if (result.IsHit()) { var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 012d9f8878..46f6276a85 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject != null) { lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => Lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + lightingColour.BindValueChanged(colour => Lighting.Colour = Result.IsHit ? colour.NewValue : Color4.Transparent, true); } else { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 9abcef83c4..21c7d49961 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -250,7 +250,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { // rather than doing it this way, we should probably attach the sample to the tail circle. // this can only be done after we stop using LegacyLastTick. - if (TailCircle.Result.Type != HitResult.Miss) + if (TailCircle.IsHit) base.PlaySamples(); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 286feac5ba..677e63c993 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!(obj is DrawableDrumRollTick)) return; - if (result.Type > HitResult.Miss) + if (result.IsHit) rollingHits++; else rollingHits--; diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs index dd3c2289ea..f7a1d130eb 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring private double hpMultiplier; /// - /// HP multiplier for a . + /// HP multiplier for a that does not satisfy . /// private double hpMissMultiplier; @@ -45,6 +45,6 @@ namespace osu.Game.Rulesets.Taiko.Scoring } protected override double GetHealthIncreaseFor(JudgementResult result) - => base.GetHealthIncreaseFor(result) * (result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier); + => base.GetHealthIncreaseFor(result) * (result.IsHit ? hpMultiplier : hpMissMultiplier); } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs index 928072c491..e029040ef3 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyTaikoScroller.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning if (r?.Type.AffectsCombo() == false) return; - passing = r == null || r.Type > HitResult.Miss; + passing = r == null || r.IsHit; foreach (var sprite in InternalChildren.OfType()) sprite.Passing = passing; diff --git a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs index 7b8ab89233..3bd20e4bb4 100644 --- a/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/DefaultHitExplosion.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.UI Alpha = 0.15f; Masking = true; - if (result == HitResult.Miss) + if (!result.IsHit()) return; bool isRim = (judgedObject.HitObject as Hit)?.Type == HitType.Rim; diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 29c25f20a4..9af7ae12a2 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -511,7 +511,7 @@ namespace osu.Game.Rulesets.Objects.Drawables case HitResult.None: break; - case HitResult.Miss: + case { } result when !result.IsHit(): updateState(ArmedState.Miss); break; diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 7736541c92..aff5a36c81 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Play.HUD public void Flash(JudgementResult result) { - if (result.Type == HitResult.Miss) + if (!result.IsHit) return; Scheduler.AddOnce(flash); diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 45fdc3ff33..aa2a83774e 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss).ToList(); + this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()).ToList(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index 18a2238784..055db143d1 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IEnumerable hitEvents) : base("Unstable Rate") { - var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss) + var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) .Select(ev => ev.TimeOffset).ToArray(); Value = 10 * standardDeviation(timeOffsets); } From 1f0620ffd49921a28a4edf9abcc61805f97d3243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 2 Oct 2020 22:58:10 +0200 Subject: [PATCH 139/164] Replace assignment references to HitResult.Miss with Judgement.MinResult --- .../Objects/Drawables/DrawableHoldNoteTail.cs | 2 +- .../Objects/Drawables/DrawableManiaHitObject.cs | 3 +-- osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs | 2 +- .../Objects/Drawables/DrawableOsuHitObject.cs | 3 +-- .../Objects/Drawables/DrawableOsuJudgement.cs | 1 - osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 1 - osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs | 4 ++-- osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs | 4 +--- osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs | 1 - 12 files changed, 10 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs index 31e43d3ee2..c780c0836e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTail.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 08c41b0d75..27960b3f3a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -136,7 +135,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); } public abstract class DrawableManiaHitObject : DrawableManiaHitObject diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 973dc06e05..b3402d13e4 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 6d6bd7fc97..b5ac26c824 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -125,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 2946331bc6..45c664ba3b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss); + public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 46f6276a85..49535e7fff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -8,7 +8,6 @@ using osu.Game.Configuration; using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 21c7d49961..280ca33234 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.Scoring; using osuTK.Graphics; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index fe7cb278b0..130b4e6e53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables else if (Progress > .75) r.Type = HitResult.Meh; else if (Time.Current >= Spinner.EndTime) - r.Type = HitResult.Miss; + r.Type = r.Judgement.MinResult; }); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 677e63c993..8f268dc1c7 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables ApplyResult(r => r.Type = countHit >= HitObject.RequiredGreatHits ? HitResult.Great : HitResult.Ok); } else - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); } protected override void UpdateStateTransforms(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 03df28f850..bb42240f25 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { if (!HitObject.HitWindows.CanBeHit(timeOffset)) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); return; } @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables return; if (!validActionPressed) - ApplyResult(r => r.Type = HitResult.Miss); + ApplyResult(r => r.Type = r.Judgement.MinResult); else ApplyResult(r => r.Type = result); } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 11ff0729e2..8ee4a5db71 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -211,9 +211,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables tick.TriggerResult(false); } - var hitResult = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : HitResult.Miss; - - ApplyResult(r => r.Type = hitResult); + ApplyResult(r => r.Type = numHits > HitObject.RequiredHits / 2 ? HitResult.Ok : r.Judgement.MinResult); } } diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index aff5a36c81..fc4a1a5d83 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -13,7 +13,6 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Play.HUD { From 2ddfd799230c0336bb3ffa6b215281032e996ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 08:09:10 +0200 Subject: [PATCH 140/164] Replace object pattern match with simple conditional --- .../Objects/Drawables/DrawableHitObject.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 9af7ae12a2..66fc61720a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -506,19 +506,8 @@ namespace osu.Game.Rulesets.Objects.Drawables Result.TimeOffset = Math.Min(HitObject.HitWindows.WindowFor(HitResult.Miss), Time.Current - endTime); - switch (Result.Type) - { - case HitResult.None: - break; - - case { } result when !result.IsHit(): - updateState(ArmedState.Miss); - break; - - default: - updateState(ArmedState.Hit); - break; - } + if (Result.HasResult) + updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss); OnNewResult?.Invoke(this, Result); } From feb39920c5d9e3d5353ab71eb5be230767af1273 Mon Sep 17 00:00:00 2001 From: tytydraco Date: Sat, 3 Oct 2020 00:48:49 -0700 Subject: [PATCH 141/164] Allow rotation lock on Android to function properly According to Google's documentation, fullSensor will ignore rotation locking preferences, while fullUser will obey them. Signed-off-by: tytydraco --- osu.Android/OsuGameActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 9839d16030..db73bb7e7f 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -9,7 +9,7 @@ using osu.Framework.Android; namespace osu.Android { - [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] + [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { protected override Framework.Game CreateGame() => new OsuGameAndroid(); From 309714081fa99023d06560f19b293acee4783df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:08:51 +0200 Subject: [PATCH 142/164] Make new health increase values mania-specific --- .../Judgements/ManiaJudgement.cs | 30 +++++++++++++++++++ osu.Game/Rulesets/Judgements/Judgement.cs | 10 +++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 220dedc4a4..d28b7bdf58 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -2,10 +2,40 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Judgements { public class ManiaJudgement : Judgement { + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.LargeTickHit: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.LargeTickMiss: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.Meh: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; + + case HitResult.Ok: + return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; + + case HitResult.Good: + return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + + case HitResult.Great: + return DEFAULT_MAX_HEALTH_INCREASE * 0.8; + + case HitResult.Perfect: + return DEFAULT_MAX_HEALTH_INCREASE; + + default: + return base.HealthIncreaseFor(result); + } + } } } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 4ee0ce437c..5d7444e9b0 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -124,19 +124,19 @@ namespace osu.Game.Rulesets.Judgements return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Meh: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; case HitResult.Ok: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.3; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.Good: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE * 0.75; case HitResult.Great: - return DEFAULT_MAX_HEALTH_INCREASE * 0.8; + return DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Perfect: - return DEFAULT_MAX_HEALTH_INCREASE; + return DEFAULT_MAX_HEALTH_INCREASE * 1.05; case HitResult.SmallBonus: return DEFAULT_MAX_HEALTH_INCREASE * 0.1; From 601675db073b9f12f83a20f8462825ef3d19a725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:10:08 +0200 Subject: [PATCH 143/164] Adjust health increase values to match old ones better --- osu.Game/Rulesets/Judgements/Judgement.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index 5d7444e9b0..89a3a2b855 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -109,16 +109,16 @@ namespace osu.Game.Rulesets.Judgements return 0; case HitResult.SmallTickHit: - return DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.SmallTickMiss: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.05; + return -DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.LargeTickHit: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE; case HitResult.LargeTickMiss: - return -DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return -DEFAULT_MAX_HEALTH_INCREASE; case HitResult.Miss: return -DEFAULT_MAX_HEALTH_INCREASE; @@ -139,10 +139,10 @@ namespace osu.Game.Rulesets.Judgements return DEFAULT_MAX_HEALTH_INCREASE * 1.05; case HitResult.SmallBonus: - return DEFAULT_MAX_HEALTH_INCREASE * 0.1; + return DEFAULT_MAX_HEALTH_INCREASE * 0.5; case HitResult.LargeBonus: - return DEFAULT_MAX_HEALTH_INCREASE * 0.2; + return DEFAULT_MAX_HEALTH_INCREASE; } } From db31280671cf969e09000ac135455094dc1c012d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:10:28 +0200 Subject: [PATCH 144/164] Award health for completed slider tails --- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index aff3f38e17..3afd36669f 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects public class SliderTailJudgement : OsuJudgement { - public override HitResult MaxResult => HitResult.IgnoreHit; + public override HitResult MaxResult => HitResult.SmallTickHit; } } } From 682b5fb056ae9ecc50dd863b6490f851d1508a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 11:41:26 +0200 Subject: [PATCH 145/164] Adjust health increase for drum roll tick to match new max result --- .../Judgements/TaikoDrumRollTickJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 0551df3211..647ad7853d 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Judgements { switch (result) { - case HitResult.Great: + case HitResult.SmallTickHit: return 0.15; default: From 7e7f225eee6bd5ea896944d65c509d5b87f1bf95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 12:34:34 +0200 Subject: [PATCH 146/164] Adjust slider input test to match new judgement result --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index d5c3538c81..1810ef4353 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -314,11 +314,11 @@ namespace osu.Game.Rulesets.Osu.Tests private bool assertMaxJudge() => judgementResults.Any() && judgementResults.All(t => t.Type == t.Judgement.MaxResult); - private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.IgnoreHit && judgementResults.First().Type == HitResult.Miss; + private bool assertHeadMissTailTracked() => judgementResults[^2].Type == HitResult.SmallTickHit && judgementResults.First().Type == HitResult.Miss; - private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.IgnoreHit; + private bool assertMidSliderJudgements() => judgementResults[^2].Type == HitResult.SmallTickHit; - private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.IgnoreMiss; + private bool assertMidSliderJudgementFail() => judgementResults[^2].Type == HitResult.SmallTickMiss; private ScoreAccessibleReplayPlayer currentPlayer; From 5888ecdeb16a6db3d8acd2825f086c0fd323d5a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 01:08:24 +0900 Subject: [PATCH 147/164] Fix spinner crashing on rewind --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index fe7cb278b0..0f249c8bbf 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -268,7 +268,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables while (wholeSpins != spins) { - var tick = ticks.FirstOrDefault(t => !t.IsHit); + var tick = ticks.FirstOrDefault(t => !t.Result.HasResult); // tick may be null if we've hit the spin limit. if (tick != null) From 26eff0120db42daed55b375cb22db982b8d60d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Oct 2020 21:11:34 +0200 Subject: [PATCH 148/164] Apply same fix for miss-triggering case See 5888ecd - the same fix is applied here, but in the miss case. --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 0f249c8bbf..5e1b7bdcae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; // Trigger a miss result for remaining ticks to avoid infinite gameplay. - foreach (var tick in ticks.Where(t => !t.IsHit)) + foreach (var tick in ticks.Where(t => !t.Result.HasResult)) tick.TriggerResult(false); ApplyResult(r => From ad42ce5639d5ae92dd6fc4e9bc067f446da04214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Oct 2020 14:50:25 +0200 Subject: [PATCH 149/164] Add failing test cases --- osu.Game.Tests/NonVisual/GameplayClockTest.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/GameplayClockTest.cs diff --git a/osu.Game.Tests/NonVisual/GameplayClockTest.cs b/osu.Game.Tests/NonVisual/GameplayClockTest.cs new file mode 100644 index 0000000000..3fd7c364b7 --- /dev/null +++ b/osu.Game.Tests/NonVisual/GameplayClockTest.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Timing; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class GameplayClockTest + { + [TestCase(0)] + [TestCase(1)] + public void TestTrueGameplayRateWithZeroAdjustment(double underlyingClockRate) + { + var framedClock = new FramedClock(new ManualClock { Rate = underlyingClockRate }); + var gameplayClock = new TestGameplayClock(framedClock); + + gameplayClock.MutableNonGameplayAdjustments.Add(new BindableDouble()); + + Assert.That(gameplayClock.TrueGameplayRate, Is.EqualTo(0)); + } + + private class TestGameplayClock : GameplayClock + { + public List> MutableNonGameplayAdjustments { get; } = new List>(); + + public override IEnumerable> NonGameplayAdjustments => MutableNonGameplayAdjustments; + + public TestGameplayClock(IFrameBasedClock underlyingClock) + : base(underlyingClock) + { + } + } + } +} From 6f2b991b329cb9b44980995e668ecc5d7e5980a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 4 Oct 2020 14:51:27 +0200 Subject: [PATCH 150/164] Ensure true gameplay rate is finite when paused externally --- osu.Game/Screens/Play/GameplayClock.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 9d04722c12..9f2868573e 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Timing; +using osu.Framework.Utils; namespace osu.Game.Screens.Play { @@ -47,7 +48,12 @@ namespace osu.Game.Screens.Play double baseRate = Rate; foreach (var adjustment in NonGameplayAdjustments) + { + if (Precision.AlmostEquals(adjustment.Value, 0)) + return 0; + baseRate /= adjustment.Value; + } return baseRate; } From 02e4f3ddafc4678df1965523d29296f058523708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:47:16 +0900 Subject: [PATCH 151/164] Fix the editor saving new beatmaps even when the user chooses not to --- osu.Game/Screens/Edit/Editor.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a0692d94e6..956b77b0d4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -84,6 +84,8 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; + private bool isNewBeatmap; + protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -113,8 +115,6 @@ namespace osu.Game.Screens.Edit // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); - bool isNewBeatmap = false; - if (Beatmap.Value is DummyWorkingBeatmap) { isNewBeatmap = true; @@ -287,6 +287,9 @@ namespace osu.Game.Screens.Edit protected void Save() { + // no longer new after first user-triggered save. + isNewBeatmap = false; + // apply any set-level metadata changes. beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); @@ -435,7 +438,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && HasUnsavedChanges && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) + if (!exitConfirmed && dialogOverlay != null && (isNewBeatmap || HasUnsavedChanges) && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) { dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); return true; @@ -456,6 +459,12 @@ namespace osu.Game.Screens.Edit private void confirmExit() { + if (isNewBeatmap) + { + // confirming exit without save means we should delete the new beatmap completely. + beatmapManager.Delete(playableBeatmap.BeatmapInfo.BeatmapSet); + } + exitConfirmed = true; this.Exit(); } From 1b02c814d6ba33141cc71461ffc876c20e97035b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:47:47 +0900 Subject: [PATCH 152/164] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 78ceaa8616..d7817cf4cf 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3a839ac1a4..fa2135580d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 31f1af135d..20a51e5feb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 9ca0e48accc80a4778c60b7049e994a7abd4d58e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:57:28 +0900 Subject: [PATCH 153/164] Change exit logic to be more test-friendly --- osu.Game/Screens/Edit/Editor.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 956b77b0d4..875ab25003 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -438,10 +438,20 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed && dialogOverlay != null && (isNewBeatmap || HasUnsavedChanges) && !(dialogOverlay.CurrentDialog is PromptForSaveDialog)) + if (!exitConfirmed) { - dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); - return true; + // if the confirm dialog is already showing (or we can't show it, ie. in tests) exit without save. + if (dialogOverlay == null || dialogOverlay.CurrentDialog is PromptForSaveDialog) + { + confirmExit(); + return true; + } + + if (isNewBeatmap || HasUnsavedChanges) + { + dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave)); + return true; + } } Background.FadeColour(Color4.White, 500); From 432ba7cdf953f1806b914769518d0e6f1cf3c23c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 4 Oct 2020 23:57:35 +0900 Subject: [PATCH 154/164] Add test coverage of exit-without-save --- .../Editing/TestSceneEditorBeatmapCreation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 720cf51f2c..13a3195824 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -5,6 +5,8 @@ using System; using System.IO; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -22,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing protected override bool EditorComponentsReady => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true; + [Resolved] + private BeatmapManager beatmapManager { get; set; } + public override void SetUpSteps() { AddStep("set dummy", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)); @@ -38,6 +43,15 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("save beatmap", () => Editor.Save()); AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); + AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false); + } + + [Test] + public void TestExitWithoutSave() + { + AddStep("exit without save", () => Editor.Exit()); + AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == true); } [Test] From e1c4c8f3d5401ce298c4f3392a1464103a931385 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 13:16:45 +0900 Subject: [PATCH 155/164] Add failing test coverage of gameplay sample pausing (during seek) --- .../TestSceneGameplaySamplePlayback.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs new file mode 100644 index 0000000000..3ab4df20df --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics.Audio; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osu.Game.Skinning; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneGameplaySamplePlayback : PlayerTestScene + { + [Test] + public void TestAllSamplesStopDuringSeek() + { + DrawableSlider slider = null; + DrawableSample[] samples = null; + ISamplePlaybackDisabler gameplayClock = null; + + AddStep("get variables", () => + { + gameplayClock = Player.ChildrenOfType().First().GameplayClock; + slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); + samples = slider.ChildrenOfType().ToArray(); + }); + + AddUntilStep("wait for slider sliding then seek", () => + { + if (!slider.Tracking.Value) + return false; + + if (!samples.Any(s => s.Playing)) + return false; + + Player.ChildrenOfType().First().Seek(40000); + return true; + }); + + AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + + // because we are in frame stable context, it's quite likely that not all samples are "played" at this point. + // the important thing is that at least one started, and that sample has since stopped. + AddAssert("no samples are playing", () => Player.ChildrenOfType().All(s => !s.IsPlaying)); + + AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value); + + AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value); + AddUntilStep("any sample is playing", () => Player.ChildrenOfType().Any(s => s.IsPlaying)); + } + + protected override bool Autoplay => true; + + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + } +} From 2a46f905ff130c465676019d2e9daed638543870 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:45:37 +0900 Subject: [PATCH 156/164] Remove unnecessary IsSeeking checks from taiko drum implementation --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 5966b24b34..1ca1be1bdf 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -163,16 +163,14 @@ namespace osu.Game.Rulesets.Taiko.UI target = centreHit; back = centre; - if (gameplayClock?.IsSeeking != true) - drumSample.Centre?.Play(); + drumSample.Centre?.Play(); } else if (action == RimAction) { target = rimHit; back = rim; - if (gameplayClock?.IsSeeking != true) - drumSample.Rim?.Play(); + drumSample.Rim?.Play(); } if (target != null) From af7d10afe0f532e7d339a0c28675817cd0b11226 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:45:57 +0900 Subject: [PATCH 157/164] Fix FrameStabilityContainer not re-caching its GameplayClock correctly --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 55c4edfbd1..668cbbdc35 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.UI public GameplayClock GameplayClock => stabilityGameplayClock; [Cached(typeof(GameplayClock))] + [Cached(typeof(ISamplePlaybackDisabler))] private readonly StabilityGameplayClock stabilityGameplayClock; public FrameStabilityContainer(double gameplayStartTime = double.MinValue) From e4710f82ec5a06258970ec01d9073838b8d7e581 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:46:15 +0900 Subject: [PATCH 158/164] Fix sample disabled status not being updated correctly from seek state --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 4 +++- osu.Game/Screens/Play/GameplayClock.cs | 12 ++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 668cbbdc35..6956d3c31a 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -228,7 +228,9 @@ namespace osu.Game.Rulesets.UI { } - public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200; + protected override bool ShouldDisableSamplePlayback => + // handle the case where playback is catching up to real-time. + base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); } } } diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index 9f2868573e..eeea6777c6 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -28,6 +28,8 @@ namespace osu.Game.Screens.Play ///
public virtual IEnumerable> NonGameplayAdjustments => Enumerable.Empty>(); + private readonly Bindable samplePlaybackDisabled = new Bindable(); + public GameplayClock(IFrameBasedClock underlyingClock) { this.underlyingClock = underlyingClock; @@ -62,13 +64,15 @@ namespace osu.Game.Screens.Play public bool IsRunning => underlyingClock.IsRunning; /// - /// Whether an ongoing seek operation is active. + /// Whether nested samples supporting the interface should be paused. /// - public virtual bool IsSeeking => false; + protected virtual bool ShouldDisableSamplePlayback => IsPaused.Value; public void ProcessFrame() { - // we do not want to process the underlying clock. + // intentionally not updating the underlying clock (handled externally). + + samplePlaybackDisabled.Value = ShouldDisableSamplePlayback; } public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; @@ -79,6 +83,6 @@ namespace osu.Game.Screens.Play public IClock Source => underlyingClock; - public IBindable SamplePlaybackDisabled => IsPaused; + IBindable ISamplePlaybackDisabler.SamplePlaybackDisabled => samplePlaybackDisabled; } } From ae8bf8cdd4ca70eb5455b4389f4be459783b8c4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 12:47:00 +0900 Subject: [PATCH 159/164] Fix StabilityGameClock not being updated --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index 6956d3c31a..f32f8d177b 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -208,11 +208,15 @@ namespace osu.Game.Rulesets.UI private void setClock() { - // in case a parent gameplay clock isn't available, just use the parent clock. - parentGameplayClock ??= Clock; - - Clock = GameplayClock; - ProcessCustomClock = false; + if (parentGameplayClock == null) + { + // in case a parent gameplay clock isn't available, just use the parent clock. + parentGameplayClock ??= Clock; + } + else + { + Clock = GameplayClock; + } } public ReplayInputHandler ReplayInputHandler { get; set; } From 758088672cf9b7e58c8c83a5c4aebae143063d56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 15:07:46 +0900 Subject: [PATCH 160/164] Don't stop non-looping samples immediately when pausing --- .../Objects/Drawables/DrawableSlider.cs | 4 +-- .../Objects/Drawables/DrawableSpinner.cs | 4 +-- .../Objects/Drawables/DrawableHitObject.cs | 10 +++++-- osu.Game/Skinning/PausableSkinnableSound.cs | 26 +++++++++---------- 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 280ca33234..4433aac9b5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopAllSamples() + public override void StopLoopingSamples() { - base.StopAllSamples(); + base.StopLoopingSamples(); slidingSample?.Stop(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 130b4e6e53..dda0c94982 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -124,9 +124,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopAllSamples() + public override void StopLoopingSamples() { - base.StopAllSamples(); + base.StopLoopingSamples(); spinningSample?.Stop(); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 66fc61720a..7a4970d172 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -18,6 +18,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; +using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -387,7 +388,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Stops playback of all samples. Automatically called when 's lifetime has been exceeded. /// - public virtual void StopAllSamples() => Samples?.Stop(); + public virtual void StopLoopingSamples() + { + if (Samples?.Looping == true) + Samples.Stop(); protected override void Update() { @@ -457,7 +461,9 @@ namespace osu.Game.Rulesets.Objects.Drawables foreach (var nested in NestedHitObjects) nested.OnKilled(); - StopAllSamples(); + // failsafe to ensure looping samples don't get stuck in a playing state. + // this could occur in a non-frame-stable context where DrawableHitObjects get killed before a SkinnableSound has the chance to be stopped. + StopLoopingSamples(); UpdateResult(false); } diff --git a/osu.Game/Skinning/PausableSkinnableSound.cs b/osu.Game/Skinning/PausableSkinnableSound.cs index 9819574b1d..d340f67575 100644 --- a/osu.Game/Skinning/PausableSkinnableSound.cs +++ b/osu.Game/Skinning/PausableSkinnableSound.cs @@ -34,21 +34,21 @@ namespace osu.Game.Skinning samplePlaybackDisabled.BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); samplePlaybackDisabled.BindValueChanged(disabled => { - if (RequestedPlaying) + if (!RequestedPlaying) return; + + // let non-looping samples that have already been started play out to completion (sounds better than abruptly cutting off). + if (!Looping) return; + + if (disabled.NewValue) + base.Stop(); + else { - if (disabled.NewValue) - base.Stop(); - // it's not easy to know if a sample has finished playing (to end). - // to keep things simple only resume playing looping samples. - else if (Looping) + // schedule so we don't start playing a sample which is no longer alive. + Schedule(() => { - // schedule so we don't start playing a sample which is no longer alive. - Schedule(() => - { - if (RequestedPlaying) - base.Play(); - }); - } + if (RequestedPlaying) + base.Play(); + }); } }); } From 9f43dedf59da624a4ea1381cedb982dce40d1b24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 15:12:34 +0900 Subject: [PATCH 161/164] Fix missing line --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 7a4970d172..11f84d370a 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -18,7 +18,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Configuration; -using osu.Game.Screens.Play; using osuTK.Graphics; namespace osu.Game.Rulesets.Objects.Drawables @@ -392,6 +391,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { if (Samples?.Looping == true) Samples.Stop(); + } protected override void Update() { From a69b1636be75e094a7323a0961a45a1703a878f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 15:18:28 +0900 Subject: [PATCH 162/164] Update tests --- .../Visual/Editing/TestSceneEditorSamplePlayback.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs index 039a21fd94..f182023c0e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSamplePlayback.cs @@ -19,12 +19,14 @@ namespace osu.Game.Tests.Visual.Editing public void TestSlidingSampleStopsOnSeek() { DrawableSlider slider = null; - DrawableSample[] samples = null; + DrawableSample[] loopingSamples = null; + DrawableSample[] onceOffSamples = null; AddStep("get first slider", () => { slider = Editor.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First(); - samples = slider.ChildrenOfType().ToArray(); + onceOffSamples = slider.ChildrenOfType().Where(s => !s.Looping).ToArray(); + loopingSamples = slider.ChildrenOfType().Where(s => s.Looping).ToArray(); }); AddStep("start playback", () => EditorClock.Start()); @@ -34,14 +36,15 @@ namespace osu.Game.Tests.Visual.Editing if (!slider.Tracking.Value) return false; - if (!samples.Any(s => s.Playing)) + if (!loopingSamples.Any(s => s.Playing)) return false; EditorClock.Seek(20000); return true; }); - AddAssert("slider samples are not playing", () => samples.Length == 5 && samples.All(s => s.Played && !s.Playing)); + AddAssert("non-looping samples are playing", () => onceOffSamples.Length == 4 && loopingSamples.All(s => s.Played || s.Playing)); + AddAssert("looping samples are not playing", () => loopingSamples.Length == 1 && loopingSamples.All(s => s.Played && !s.Playing)); } } } From 0605bb9b8d565b843dda3fdbf9702474fc8a5592 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 16:20:29 +0900 Subject: [PATCH 163/164] Fix incorrect parent state transfer --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index f32f8d177b..70b3d0c7d4 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -59,13 +59,16 @@ namespace osu.Game.Rulesets.UI private int direction; [BackgroundDependencyLoader(true)] - private void load(GameplayClock clock) + private void load(GameplayClock clock, ISamplePlaybackDisabler sampleDisabler) { if (clock != null) { parentGameplayClock = stabilityGameplayClock.ParentGameplayClock = clock; GameplayClock.IsPaused.BindTo(clock.IsPaused); } + + // this is a bit temporary. should really be done inside of GameplayClock (but requires large structural changes). + stabilityGameplayClock.ParentSampleDisabler = sampleDisabler; } protected override void LoadComplete() @@ -225,6 +228,8 @@ namespace osu.Game.Rulesets.UI { public GameplayClock ParentGameplayClock; + public ISamplePlaybackDisabler ParentSampleDisabler; + public override IEnumerable> NonGameplayAdjustments => ParentGameplayClock?.NonGameplayAdjustments ?? Enumerable.Empty>(); public StabilityGameplayClock(FramedClock underlyingClock) @@ -234,7 +239,9 @@ namespace osu.Game.Rulesets.UI protected override bool ShouldDisableSamplePlayback => // handle the case where playback is catching up to real-time. - base.ShouldDisableSamplePlayback || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); + base.ShouldDisableSamplePlayback + || ParentSampleDisabler?.SamplePlaybackDisabled.Value == true + || (ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200); } } } From c622adde7a9374459a3a9a6ed93b7064685eb14d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 5 Oct 2020 16:24:02 +0900 Subject: [PATCH 164/164] Rename method back and add xmldoc --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 7 ++++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 4433aac9b5..280ca33234 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -109,9 +109,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopLoopingSamples() + public override void StopAllSamples() { - base.StopLoopingSamples(); + base.StopAllSamples(); slidingSample?.Stop(); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index dda0c94982..130b4e6e53 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -124,9 +124,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public override void StopLoopingSamples() + public override void StopAllSamples() { - base.StopLoopingSamples(); + base.StopAllSamples(); spinningSample?.Stop(); } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 11f84d370a..8012b4d95c 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -385,9 +385,10 @@ namespace osu.Game.Rulesets.Objects.Drawables } /// - /// Stops playback of all samples. Automatically called when 's lifetime has been exceeded. + /// Stops playback of all relevant samples. Generally only looping samples should be stopped by this, and the rest let to play out. + /// Automatically called when 's lifetime has been exceeded. /// - public virtual void StopLoopingSamples() + public virtual void StopAllSamples() { if (Samples?.Looping == true) Samples.Stop(); @@ -463,7 +464,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // failsafe to ensure looping samples don't get stuck in a playing state. // this could occur in a non-frame-stable context where DrawableHitObjects get killed before a SkinnableSound has the chance to be stopped. - StopLoopingSamples(); + StopAllSamples(); UpdateResult(false); }