diff --git a/osu.Android.props b/osu.Android.props
index 650ebde54d..eaad4daf35 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,6 +52,6 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
index e7ce680365..2ee7cea645 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs
@@ -4,12 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
-using osu.Game.Scoring.Legacy;
namespace osu.Game.Rulesets.Catch.Difficulty
{
@@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
mods = Score.Mods;
- fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
- ticksHit = Score?.GetCount100() ?? 0;
- tinyTicksHit = Score?.GetCount50() ?? 0;
- tinyTicksMissed = Score?.GetCountKatu() ?? 0;
- misses = Score.Statistics[HitResult.Miss];
+ fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect);
+ ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
+ tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
+ tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
+ misses = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
@@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
// Longer maps are worth more
double lengthBonus =
- 0.95f + 0.3f * Math.Min(1.0f, numTotalHits / 2500.0f) +
- (numTotalHits > 2500 ? (float)Math.Log10(numTotalHits / 2500.0f) * 0.475f : 0.0f);
+ 0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
+ (numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
// Longer maps are worth more
value *= lengthBonus;
@@ -65,14 +65,14 @@ namespace osu.Game.Rulesets.Catch.Difficulty
if (Attributes.MaxCombo > 0)
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
- float approachRate = (float)Attributes.ApproachRate;
- float approachRateFactor = 1.0f;
- if (approachRate > 9.0f)
- approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9
- if (approachRate > 10.0f)
- approachRateFactor += 0.1f * (approachRate - 10.0f); // Additional 10% at AR 11, 30% total
- else if (approachRate < 8.0f)
- approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8
+ double approachRate = Attributes.ApproachRate;
+ double approachRateFactor = 1.0;
+ if (approachRate > 9.0)
+ approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
+ if (approachRate > 10.0)
+ approachRateFactor += 0.1 * (approachRate - 10.0); // Additional 10% at AR 11, 30% total
+ else if (approachRate < 8.0)
+ approachRateFactor += 0.025 * (8.0 - approachRate); // 2.5% for each AR below 8
value *= approachRateFactor;
@@ -80,10 +80,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
{
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
// Hiddens gives almost nothing on max approach rate, and more the lower it is
- if (approachRate <= 10.0f)
- value *= 1.05f + 0.075f * (10.0f - approachRate); // 7.5% for each AR below 10
- else if (approachRate > 10.0f)
- value *= 1.01f + 0.04f * (11.0f - Math.Min(11.0f, approachRate)); // 5% at AR 10, 1% at AR 11
+ if (approachRate <= 10.0)
+ value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
+ else if (approachRate > 10.0)
+ value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
}
if (mods.Any(m => m is ModFlashlight))
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return value;
}
- private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1);
+ private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
private int totalComboHits() => misses + ticksHit + fruitsHit;
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
index 3f7a2baedd..91383c5548 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -37,12 +38,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
- countPerfect = Score.Statistics[HitResult.Perfect];
- countGreat = Score.Statistics[HitResult.Great];
- countGood = Score.Statistics[HitResult.Good];
- countOk = Score.Statistics[HitResult.Ok];
- countMeh = Score.Statistics[HitResult.Meh];
- countMiss = Score.Statistics[HitResult.Miss];
+ countPerfect = Score.Statistics.GetOrDefault(HitResult.Perfect);
+ 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);
if (mods.Any(m => !m.Ranked))
return 0;
diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
index 549f0f9214..289f8a00ef 100644
--- a/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
+++ b/osu.Game.Rulesets.Mania/Scoring/ManiaHitWindows.cs
@@ -7,5 +7,20 @@ namespace osu.Game.Rulesets.Mania.Scoring
{
public class ManiaHitWindows : HitWindows
{
+ public override bool IsHitResultAllowed(HitResult result)
+ {
+ switch (result)
+ {
+ case HitResult.Perfect:
+ case HitResult.Great:
+ case HitResult.Good:
+ case HitResult.Ok:
+ case HitResult.Meh:
+ case HitResult.Miss:
+ return true;
+ }
+
+ return false;
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
index ce8ecf02ac..6f4c0f9cfa 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -45,10 +46,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
- countGreat = Score.Statistics[HitResult.Great];
- countGood = Score.Statistics[HitResult.Good];
- countMeh = Score.Statistics[HitResult.Meh];
- countMiss = Score.Statistics[HitResult.Miss];
+ countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
+ countGood = Score.Statistics.GetOrDefault(HitResult.Good);
+ countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
+ countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
@@ -180,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
int amountHitObjectsWithAccuracy = countHitCircles;
if (amountHitObjectsWithAccuracy > 0)
- betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6);
+ betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (double)(amountHitObjectsWithAccuracy * 6);
else
betterAccuracyPercentage = 0;
@@ -203,7 +204,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return accuracyValue;
}
- private double totalHits => countGreat + countGood + countMeh + countMiss;
- private double totalSuccessfulHits => countGreat + countGood + countMeh;
+ private int totalHits => countGreat + countGood + countMeh + countMiss;
+ private int totalSuccessfulHits => countGreat + countGood + countMeh;
}
}
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index 8c26ca70ac..f7729138ff 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
[NonParallelizable]
[TestCase("basic")]
[TestCase("slider-generating-drumroll")]
+ [TestCase("sample-to-type-conversions")]
public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
@@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
public struct ConvertValue : IEquatable
{
///
- /// A sane value to account for osu!stable using ints everwhere.
+ /// A sane value to account for osu!stable using ints everywhere.
///
private const float conversion_lenience = 2;
diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
new file mode 100644
index 0000000000..d541aa8de8
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSampleOutput.cs
@@ -0,0 +1,49 @@
+// 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.Testing;
+using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Taiko.Objects.Drawables;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Taiko.Tests
+{
+ ///
+ /// Taiko has some interesting rules for legacy mappings.
+ ///
+ [HeadlessTest]
+ public class TestSceneSampleOutput : PlayerTestScene
+ {
+ public TestSceneSampleOutput()
+ : base(new TaikoRuleset())
+ {
+ }
+
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+ AddAssert("has correct samples", () =>
+ {
+ var names = Player.DrawableRuleset.Playfield.AllHitObjects.OfType().Select(h => string.Join(',', h.GetSamples().Select(s => s.Name)));
+
+ var expected = new[]
+ {
+ string.Empty,
+ string.Empty,
+ string.Empty,
+ string.Empty,
+ HitSampleInfo.HIT_FINISH,
+ HitSampleInfo.HIT_WHISTLE,
+ HitSampleInfo.HIT_WHISTLE,
+ HitSampleInfo.HIT_WHISTLE,
+ };
+
+ return names.SequenceEqual(expected);
+ });
+ }
+
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TaikoBeatmapConversionTest().GetBeatmap("sample-to-type-conversions");
+ }
+}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
index 3a0fb64622..bc147b53ac 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -31,10 +32,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
public override double Calculate(Dictionary categoryDifficulty = null)
{
mods = Score.Mods;
- countGreat = Score.Statistics[HitResult.Great];
- countGood = Score.Statistics[HitResult.Good];
- countMeh = Score.Statistics[HitResult.Meh];
- countMiss = Score.Statistics[HitResult.Miss];
+ countGreat = Score.Statistics.GetOrDefault(HitResult.Great);
+ countGood = Score.Statistics.GetOrDefault(HitResult.Good);
+ countMeh = Score.Statistics.GetOrDefault(HitResult.Meh);
+ countMiss = Score.Statistics.GetOrDefault(HitResult.Miss);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
index d332f90cd4..81b969eaf3 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs
@@ -49,10 +49,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
? new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.CentreHit), _ => new CentreHitCirclePiece(), confineMode: ConfineMode.ScaleToFit)
: new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.RimHit), _ => new RimHitCirclePiece(), confineMode: ConfineMode.ScaleToFit);
- protected override IEnumerable GetSamples()
+ public override IEnumerable GetSamples()
{
// normal and claps are always handled by the drum (see DrumSampleMapping).
- var samples = HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP);
+ // in addition, whistles are excluded as they are an alternative rim marker.
+
+ var samples = HitObject.Samples.Where(s =>
+ s.Name != HitSampleInfo.HIT_NORMAL
+ && s.Name != HitSampleInfo.HIT_CLAP
+ && s.Name != HitSampleInfo.HIT_WHISTLE);
if (HitObject.Type == HitType.Rim && HitObject.IsStrong)
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
index 90daf3950c..3ab09d4cbe 100644
--- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs
@@ -166,7 +166,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
// Most osu!taiko hitsounds are managed by the drum (see DrumSampleMapping).
- protected override IEnumerable GetSamples() => Enumerable.Empty();
+ public override IEnumerable GetSamples() => Enumerable.Empty();
protected abstract SkinnableDrawable CreateMainPiece();
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json
new file mode 100644
index 0000000000..47ca6aef68
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions-expected-conversion.json
@@ -0,0 +1,116 @@
+{
+ "Mappings": [
+ {
+ "StartTime": 110.0,
+ "Objects": [
+ {
+ "StartTime": 110.0,
+ "EndTime": 110.0,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 538.0,
+ "Objects": [
+ {
+ "StartTime": 538.0,
+ "EndTime": 538.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 967.0,
+ "Objects": [
+ {
+ "StartTime": 967.0,
+ "EndTime": 967.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 1395.0,
+ "Objects": [
+ {
+ "StartTime": 1395.0,
+ "EndTime": 1395.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": false
+ }
+ ]
+ },
+ {
+ "StartTime": 1824.0,
+ "Objects": [
+ {
+ "StartTime": 1824.0,
+ "EndTime": 1824.0,
+ "IsRim": false,
+ "IsCentre": true,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ },
+ {
+ "StartTime": 2252.0,
+ "Objects": [
+ {
+ "StartTime": 2252.0,
+ "EndTime": 2252.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ },
+ {
+ "StartTime": 2681.0,
+ "Objects": [
+ {
+ "StartTime": 2681.0,
+ "EndTime": 2681.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ },
+ {
+ "StartTime": 3110.0,
+ "Objects": [
+ {
+ "StartTime": 3110.0,
+ "EndTime": 3110.0,
+ "IsRim": true,
+ "IsCentre": false,
+ "IsDrumRoll": false,
+ "IsSwell": false,
+ "IsStrong": true
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu
new file mode 100644
index 0000000000..a3537e7149
--- /dev/null
+++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/sample-to-type-conversions.osu
@@ -0,0 +1,62 @@
+osu file format v14
+
+[General]
+AudioFilename: audio.mp3
+AudioLeadIn: 0
+PreviewTime: -1
+Countdown: 0
+SampleSet: Normal
+StackLeniency: 0.5
+Mode: 1
+LetterboxInBreaks: 0
+WidescreenStoryboard: 1
+
+[Editor]
+Bookmarks: 110,13824,54967,82395,109824
+DistanceSpacing: 0.1
+BeatDivisor: 4
+GridSize: 32
+TimelineZoom: 3.099999
+
+[Metadata]
+Title:test
+TitleUnicode:test
+Artist:sample conversion
+ArtistUnicode:sample conversion
+Creator:banchobot
+Version:sample test
+Source:
+Tags:
+BeatmapID:0
+BeatmapSetID:-1
+
+[Difficulty]
+HPDrainRate:6
+CircleSize:2
+OverallDifficulty:6
+ApproachRate:7
+SliderMultiplier:1.4
+SliderTickRate:4
+
+[Events]
+//Background and Video events
+//Break Periods
+//Storyboard Layer 0 (Background)
+//Storyboard Layer 1 (Fail)
+//Storyboard Layer 2 (Pass)
+//Storyboard Layer 3 (Foreground)
+//Storyboard Layer 4 (Overlay)
+//Storyboard Sound Samples
+
+[TimingPoints]
+110,428.571428571429,4,1,0,100,1,0
+
+[HitObjects]
+256,192,110,5,0,0:0:0:0:
+256,192,538,1,8,0:0:0:0:
+256,192,967,1,2,0:0:0:0:
+256,192,1395,1,10,0:0:0:0:
+256,192,1824,1,4,0:0:0:0:
+256,192,2252,1,12,0:0:0:0:
+256,192,2681,1,6,0:0:0:0:
+256,192,3110,1,14,0:0:0:0:
diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
index 38026517d9..06ccd45cb8 100644
--- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
+++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs
@@ -12,6 +12,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Audio;
+using osu.Game.Screens.Play;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko.UI
@@ -145,6 +146,9 @@ namespace osu.Game.Rulesets.Taiko.UI
centreHit.Colour = colours.Pink;
}
+ [Resolved(canBeNull: true)]
+ private GameplayClock gameplayClock { get; set; }
+
public bool OnPressed(TaikoAction action)
{
Drawable target = null;
@@ -157,14 +161,16 @@ namespace osu.Game.Rulesets.Taiko.UI
target = centreHit;
back = centre;
- drumSample.Centre?.Play();
+ if (gameplayClock?.IsSeeking != true)
+ drumSample.Centre?.Play();
}
else if (action == RimAction)
{
target = rimHit;
back = rim;
- drumSample.Rim?.Play();
+ if (gameplayClock?.IsSeeking != true)
+ drumSample.Rim?.Play();
}
if (target != null)
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index 2fdeadca02..9ebedb3c80 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var storyboard = decoder.Decode(stream);
Assert.IsTrue(storyboard.HasDrawable);
- Assert.AreEqual(5, storyboard.Layers.Count());
+ Assert.AreEqual(6, storyboard.Layers.Count());
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
Assert.IsNotNull(background);
@@ -56,6 +56,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(foreground.VisibleWhenPassing);
Assert.AreEqual("Foreground", foreground.Name);
+ StoryboardLayer overlay = storyboard.Layers.FirstOrDefault(l => l.Depth == int.MinValue);
+ Assert.IsNotNull(overlay);
+ Assert.IsEmpty(overlay.Elements);
+ Assert.IsTrue(overlay.VisibleWhenFailing);
+ Assert.IsTrue(overlay.VisibleWhenPassing);
+ Assert.AreEqual("Overlay", overlay.Name);
+
int spriteCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSprite));
int animationCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardAnimation));
int sampleCount = background.Elements.Count(x => x.GetType() == typeof(StoryboardSampleInfo));
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index ba6f5fc85c..43fab186aa 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -156,8 +156,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
// ReSharper disable once AccessToModifiedClosure
- manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
- manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount);
+ manager.ItemAdded.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
+ manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount));
var imported = await LoadOszIntoOsu(osu);
diff --git a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs
index 1f0c069f8d..bd578dcbc4 100644
--- a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs
+++ b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs
@@ -29,8 +29,22 @@ namespace osu.Game.Tests.ScrollAlgorithms
[Test]
public void TestDisplayStartTime()
{
- // Sequential scroll algorithm approximates the start time
- // This should be fixed in the future
+ // easy cases - time range adjusted for velocity fits within control point duration
+ Assert.AreEqual(2500, algorithm.GetDisplayStartTime(5000, 0, 2500, 1)); // 5000 - (2500 / 1)
+ Assert.AreEqual(13750, algorithm.GetDisplayStartTime(15000, 0, 2500, 1)); // 15000 - (2500 / 2)
+ Assert.AreEqual(20000, algorithm.GetDisplayStartTime(25000, 0, 2500, 1)); // 25000 - (2500 / 0.5)
+
+ // hard case - time range adjusted for velocity exceeds control point duration
+
+ // 1st multiplier point takes 10000 / 2500 = 4 scroll lengths
+ // 2nd multiplier point takes 10000 / (2500 / 2) = 8 scroll lengths
+ // 3rd multiplier point takes 2500 / (2500 * 2) = 0.5 scroll lengths up to hitobject start
+
+ // absolute position of the hitobject = 1000 * (4 + 8 + 0.5) = 12500
+ // minus one scroll length allowance = 12500 - 1000 = 11500 = 11.5 [scroll lengths]
+ // therefore the start time lies within the second multiplier point (because 11.5 < 4 + 8)
+ // its exact time position is = 10000 + 7.5 * (2500 / 2) = 19375
+ Assert.AreEqual(19375, algorithm.GetDisplayStartTime(22500, 0, 2500, 1000));
}
[Test]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index 0d15e495e3..2f15e549f7 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -16,6 +16,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -77,19 +78,18 @@ namespace osu.Game.Tests.Visual.Gameplay
}
};
- setUpHitObjects();
+ hitObjectSpawnDelegate?.Cancel();
});
- private void setUpHitObjects()
+ private void setUpHitObjects() => AddStep("set up hit objects", () =>
{
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate)
addHitObject(Time.Current + i);
- hitObjectSpawnDelegate?.Cancel();
hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true);
- }
+ });
private IList testControlPoints => new List
{
@@ -101,6 +101,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestScrollAlgorithms()
{
+ setUpHitObjects();
+
AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
@@ -113,6 +115,8 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestConstantScrollLifetime()
{
+ setUpHitObjects();
+
AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
// scroll container time range must be less than the rate of spawning hitobjects
// otherwise the hitobjects will spawn already partly visible on screen and look wrong
@@ -122,14 +126,40 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestSequentialScrollLifetime()
{
+ setUpHitObjects();
+
AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
}
+ [Test]
+ public void TestSlowSequentialScroll()
+ {
+ AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
+ AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range));
+ AddStep("add control points", () => addControlPoints(
+ new List
+ {
+ new MultiplierControlPoint { Velocity = 0.1 }
+ },
+ Time.Current + time_range));
+
+ // All of the hit objects added below should be immediately visible on screen
+ AddStep("add hit objects", () =>
+ {
+ for (int i = 0; i < 20; ++i)
+ {
+ addHitObject(Time.Current + time_range * (2 + 0.1 * i));
+ }
+ });
+ }
+
[Test]
public void TestOverlappingScrollLifetime()
{
+ setUpHitObjects();
+
AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0));
AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current));
@@ -221,7 +251,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestDrawableControlPoint : DrawableHitObject
{
public TestDrawableControlPoint(ScrollingDirection direction, double time)
- : base(new HitObject { StartTime = time })
+ : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Centre;
@@ -252,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private class TestDrawableHitObject : DrawableHitObject
{
public TestDrawableHitObject(double time)
- : base(new HitObject { StartTime = time })
+ : base(new HitObject { StartTime = time, HitWindows = HitWindows.Empty })
{
Origin = Anchor.Custom;
OriginPosition = new Vector2(75 / 4.0f);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 6a0f86fe53..7ed7a116b4 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
@@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneSkipOverlay : OsuManualInputManagerTestScene
{
- private SkipOverlay skip;
+ private TestSkipOverlay skip;
private int requestCount;
private double increment;
@@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
- skip = new SkipOverlay(skip_time)
+ skip = new TestSkipOverlay(skip_time)
{
RequestSkip = () =>
{
@@ -56,19 +56,19 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestFadeOnIdle()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(Vector2.Zero));
- AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
- AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
+ AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
+ AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("fully visible", () => skip.Children.First().Alpha == 1);
- AddUntilStep("wait for fade", () => skip.Children.First().Alpha < 1);
+ AddUntilStep("fully visible", () => skip.FadingContent.Alpha == 1);
+ AddUntilStep("wait for fade", () => skip.FadingContent.Alpha < 1);
}
[Test]
public void TestClickableAfterFade()
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
- AddUntilStep("wait for fade", () => skip.Children.First().Alpha == 0);
+ AddUntilStep("wait for fade", () => skip.FadingContent.Alpha == 0);
AddStep("click", () => InputManager.Click(MouseButton.Left));
checkRequestCount(1);
}
@@ -105,13 +105,25 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("move mouse", () => InputManager.MoveMouseTo(skip.ScreenSpaceDrawQuad.Centre));
AddStep("button down", () => InputManager.PressButton(MouseButton.Left));
- AddUntilStep("wait for overlay disappear", () => !skip.IsPresent);
- AddAssert("ensure button didn't disappear", () => skip.Children.First().Alpha > 0);
+ AddUntilStep("wait for overlay disappear", () => !skip.OverlayContent.IsPresent);
+ AddAssert("ensure button didn't disappear", () => skip.FadingContent.Alpha > 0);
AddStep("button up", () => InputManager.ReleaseButton(MouseButton.Left));
checkRequestCount(0);
}
private void checkRequestCount(int expected) =>
AddAssert($"request count is {expected}", () => requestCount == expected);
+
+ private class TestSkipOverlay : SkipOverlay
+ {
+ public TestSkipOverlay(double startTime)
+ : base(startTime)
+ {
+ }
+
+ public Drawable OverlayContent => InternalChild;
+
+ public Drawable FadingContent => (OverlayContent as Container)?.Child;
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
index d2e8c22c39..34c6940552 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
@@ -69,6 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
roomManager.CreateRequested = r =>
{
@@ -89,6 +90,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("setup", () =>
{
+ Room.Name.Value = "Test Room";
+ Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
+
fail = true;
roomManager.CreateRequested = _ => !fail;
});
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 34ad1df6bc..7aaf0ca08d 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures;
using osu.Framework.Lists;
@@ -38,12 +39,16 @@ namespace osu.Game.Beatmaps
///
/// Fired when a single difficulty has been hidden.
///
- public event Action BeatmapHidden;
+ public IBindable> BeatmapHidden => beatmapHidden;
+
+ private readonly Bindable> beatmapHidden = new Bindable>();
///
/// Fired when a single difficulty has been restored.
///
- public event Action BeatmapRestored;
+ public IBindable> BeatmapRestored => beatmapRestored;
+
+ private readonly Bindable> beatmapRestored = new Bindable>();
///
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
@@ -74,8 +79,8 @@ namespace osu.Game.Beatmaps
DefaultBeatmap = defaultBeatmap;
beatmaps = (BeatmapStore)ModelStore;
- beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
- beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
+ beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference(b);
+ beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b);
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
exportStorage = storage.GetStorageForDirectory("exports");
diff --git a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
index 48e8bdbb76..ea23c49c4a 100644
--- a/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
+++ b/osu.Game/Beatmaps/Legacy/LegacyStoryLayer.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Beatmaps.Legacy
Fail = 1,
Pass = 2,
Foreground = 3,
- Video = 4
+ Overlay = 4,
+ Video = 5
}
}
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 839f9075e5..33b16cbaaf 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -11,6 +11,7 @@ using Humanizer;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using osu.Framework;
+using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Logging;
@@ -56,13 +57,17 @@ namespace osu.Game.Database
/// Fired when a new becomes available in the database.
/// This is not guaranteed to run on the update thread.
///
- public event Action ItemAdded;
+ public IBindable> ItemAdded => itemAdded;
+
+ private readonly Bindable> itemAdded = new Bindable>();
///
/// Fired when a is removed from the database.
/// This is not guaranteed to run on the update thread.
///
- public event Action ItemRemoved;
+ public IBindable> ItemRemoved => itemRemoved;
+
+ private readonly Bindable> itemRemoved = new Bindable>();
public virtual string[] HandledExtensions => new[] { ".zip" };
@@ -82,8 +87,8 @@ namespace osu.Game.Database
ContextFactory = contextFactory;
ModelStore = modelStore;
- ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item));
- ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s));
+ ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference(item));
+ ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item));
Files = new FileStore(contextFactory, storage);
diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs
index 1b90898c8d..8f469ca590 100644
--- a/osu.Game/Database/DownloadableArchiveModelManager.cs
+++ b/osu.Game/Database/DownloadableArchiveModelManager.cs
@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using osu.Framework.Bindables;
namespace osu.Game.Database
{
@@ -23,9 +24,13 @@ namespace osu.Game.Database
where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable
where TFileModel : class, INamedFileInfo, new()
{
- public event Action> DownloadBegan;
+ public IBindable>> DownloadBegan => downloadBegan;
- public event Action> DownloadFailed;
+ private readonly Bindable>> downloadBegan = new Bindable>>();
+
+ public IBindable>> DownloadFailed => downloadFailed;
+
+ private readonly Bindable>> downloadFailed = new Bindable>>();
private readonly IAPIProvider api;
@@ -81,7 +86,7 @@ namespace osu.Game.Database
// for now a failed import will be marked as a failed download for simplicity.
if (!imported.Any())
- DownloadFailed?.Invoke(request);
+ downloadFailed.Value = new WeakReference>(request);
currentDownloads.Remove(request);
}, TaskCreationOptions.LongRunning);
@@ -100,14 +105,14 @@ namespace osu.Game.Database
api.PerformAsync(request);
- DownloadBegan?.Invoke(request);
+ downloadBegan.Value = new WeakReference>(request);
return true;
void triggerFailure(Exception error)
{
currentDownloads.Remove(request);
- DownloadFailed?.Invoke(request);
+ downloadFailed.Value = new WeakReference>(request);
notification.State = ProgressNotificationState.Cancelled;
diff --git a/osu.Game/Database/IModelDownloader.cs b/osu.Game/Database/IModelDownloader.cs
index 99aeb4eacf..0cb633280e 100644
--- a/osu.Game/Database/IModelDownloader.cs
+++ b/osu.Game/Database/IModelDownloader.cs
@@ -1,8 +1,9 @@
// 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.Online.API;
using System;
+using osu.Game.Online.API;
+using osu.Framework.Bindables;
namespace osu.Game.Database
{
@@ -17,13 +18,13 @@ namespace osu.Game.Database
/// Fired when a download begins.
/// This is NOT run on the update thread and should be scheduled.
///
- event Action> DownloadBegan;
+ IBindable>> DownloadBegan { get; }
///
/// Fired when a download is interrupted, either due to user cancellation or failure.
/// This is NOT run on the update thread and should be scheduled.
///
- event Action> DownloadFailed;
+ IBindable>> DownloadFailed { get; }
///
/// Checks whether a given is already available in the local store.
diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs
index 1bdbbb48e6..852b385798 100644
--- a/osu.Game/Database/IModelManager.cs
+++ b/osu.Game/Database/IModelManager.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Bindables;
namespace osu.Game.Database
{
@@ -9,11 +10,11 @@ namespace osu.Game.Database
/// Represents a model manager that publishes events when s are added or removed.
///
/// The model type.
- public interface IModelManager
+ public interface IModelManager
where TModel : class
{
- event Action ItemAdded;
+ IBindable> ItemAdded { get; }
- event Action ItemRemoved;
+ IBindable> ItemRemoved { get; }
}
}
diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs
index a3125614aa..d739f56828 100644
--- a/osu.Game/Graphics/Containers/SectionsContainer.cs
+++ b/osu.Game/Graphics/Containers/SectionsContainer.cs
@@ -189,23 +189,18 @@ namespace osu.Game.Graphics.Containers
headerBackgroundContainer.Height = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0);
headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0;
- T bestMatch = null;
- float minDiff = float.MaxValue;
float scrollOffset = FixedHeader?.LayoutSize.Y ?? 0;
+ Func diff = section => scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset;
- foreach (var section in Children)
+ if (scrollContainer.IsScrolledToEnd())
{
- float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset);
-
- if (diff < minDiff)
- {
- minDiff = diff;
- bestMatch = section;
- }
+ SelectedSection.Value = Children.LastOrDefault();
+ }
+ else
+ {
+ SelectedSection.Value = Children.TakeWhile(section => diff(section) <= 0).LastOrDefault()
+ ?? Children.FirstOrDefault();
}
-
- if (bestMatch != null)
- SelectedSection.Value = bestMatch;
}
}
diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs
index 0769be2998..47de7d75ed 100644
--- a/osu.Game/Online/DownloadTrackingComposite.cs
+++ b/osu.Game/Online/DownloadTrackingComposite.cs
@@ -34,6 +34,11 @@ namespace osu.Game.Online
Model.Value = model;
}
+ private IBindable> managerAdded;
+ private IBindable> managerRemoved;
+ private IBindable>> managerDownloadBegan;
+ private IBindable>> managerDownloadFailed;
+
[BackgroundDependencyLoader(true)]
private void load()
{
@@ -47,23 +52,39 @@ namespace osu.Game.Online
attachDownload(manager.GetExistingDownload(modelInfo.NewValue));
}, true);
- manager.DownloadBegan += downloadBegan;
- manager.DownloadFailed += downloadFailed;
- manager.ItemAdded += itemAdded;
- manager.ItemRemoved += itemRemoved;
+ managerDownloadBegan = manager.DownloadBegan.GetBoundCopy();
+ managerDownloadBegan.BindValueChanged(downloadBegan);
+ managerDownloadFailed = manager.DownloadFailed.GetBoundCopy();
+ managerDownloadFailed.BindValueChanged(downloadFailed);
+ managerAdded = manager.ItemAdded.GetBoundCopy();
+ managerAdded.BindValueChanged(itemAdded);
+ managerRemoved = manager.ItemRemoved.GetBoundCopy();
+ managerRemoved.BindValueChanged(itemRemoved);
}
- private void downloadBegan(ArchiveDownloadRequest request) => Schedule(() =>
+ private void downloadBegan(ValueChangedEvent>> weakRequest)
{
- if (request.Model.Equals(Model.Value))
- attachDownload(request);
- });
+ if (weakRequest.NewValue.TryGetTarget(out var request))
+ {
+ Schedule(() =>
+ {
+ if (request.Model.Equals(Model.Value))
+ attachDownload(request);
+ });
+ }
+ }
- private void downloadFailed(ArchiveDownloadRequest request) => Schedule(() =>
+ private void downloadFailed(ValueChangedEvent>> weakRequest)
{
- if (request.Model.Equals(Model.Value))
- attachDownload(null);
- });
+ if (weakRequest.NewValue.TryGetTarget(out var request))
+ {
+ Schedule(() =>
+ {
+ if (request.Model.Equals(Model.Value))
+ attachDownload(null);
+ });
+ }
+ }
private ArchiveDownloadRequest attachedRequest;
@@ -107,9 +128,17 @@ namespace osu.Game.Online
private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null));
- private void itemAdded(TModel s) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable);
+ private void itemAdded(ValueChangedEvent> weakItem)
+ {
+ if (weakItem.NewValue.TryGetTarget(out var item))
+ setDownloadStateFromManager(item, DownloadState.LocallyAvailable);
+ }
- private void itemRemoved(TModel s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded);
+ private void itemRemoved(ValueChangedEvent> weakItem)
+ {
+ if (weakItem.NewValue.TryGetTarget(out var item))
+ setDownloadStateFromManager(item, DownloadState.NotDownloaded);
+ }
private void setDownloadStateFromManager(TModel s, DownloadState state) => Schedule(() =>
{
@@ -125,14 +154,6 @@ namespace osu.Game.Online
{
base.Dispose(isDisposing);
- if (manager != null)
- {
- manager.DownloadBegan -= downloadBegan;
- manager.DownloadFailed -= downloadFailed;
- manager.ItemAdded -= itemAdded;
- manager.ItemRemoved -= itemRemoved;
- }
-
State.UnbindAll();
attachDownload(null);
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index 11a3834c71..c367c3b636 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -186,8 +186,17 @@ namespace osu.Game
return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList();
}
- BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true);
- BeatmapManager.ItemAdded += i => ScoreManager.Undelete(getBeatmapScores(i), true);
+ BeatmapManager.ItemRemoved.BindValueChanged(i =>
+ {
+ if (i.NewValue.TryGetTarget(out var item))
+ ScoreManager.Delete(getBeatmapScores(item), true);
+ });
+
+ BeatmapManager.ItemAdded.BindValueChanged(i =>
+ {
+ if (i.NewValue.TryGetTarget(out var item))
+ ScoreManager.Undelete(getBeatmapScores(item), true);
+ });
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs
index ded641b262..35f3cb0e25 100644
--- a/osu.Game/Overlays/MusicController.cs
+++ b/osu.Game/Overlays/MusicController.cs
@@ -60,11 +60,16 @@ namespace osu.Game.Overlays
[Resolved(canBeNull: true)]
private OnScreenDisplay onScreenDisplay { get; set; }
+ private IBindable> managerAdded;
+ private IBindable> managerRemoved;
+
[BackgroundDependencyLoader]
private void load()
{
- beatmaps.ItemAdded += handleBeatmapAdded;
- beatmaps.ItemRemoved += handleBeatmapRemoved;
+ managerAdded = beatmaps.ItemAdded.GetBoundCopy();
+ managerAdded.BindValueChanged(beatmapAdded);
+ managerRemoved = beatmaps.ItemRemoved.GetBoundCopy();
+ managerRemoved.BindValueChanged(beatmapRemoved);
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal).OrderBy(_ => RNG.Next()));
}
@@ -93,16 +98,28 @@ namespace osu.Game.Overlays
///
public bool IsPlaying => current?.Track.IsRunning ?? false;
- private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() =>
+ private void beatmapAdded(ValueChangedEvent> weakSet)
{
- if (!beatmapSets.Contains(set))
- beatmapSets.Add(set);
- });
+ if (weakSet.NewValue.TryGetTarget(out var set))
+ {
+ Schedule(() =>
+ {
+ if (!beatmapSets.Contains(set))
+ beatmapSets.Add(set);
+ });
+ }
+ }
- private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() =>
+ private void beatmapRemoved(ValueChangedEvent> weakSet)
{
- beatmapSets.RemoveAll(s => s.ID == set.ID);
- });
+ if (weakSet.NewValue.TryGetTarget(out var set))
+ {
+ Schedule(() =>
+ {
+ beatmapSets.RemoveAll(s => s.ID == set.ID);
+ });
+ }
+ }
private ScheduledDelegate seekDelegate;
@@ -299,17 +316,6 @@ namespace osu.Game.Overlays
}
}
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (beatmaps != null)
- {
- beatmaps.ItemAdded -= handleBeatmapAdded;
- beatmaps.ItemRemoved -= handleBeatmapRemoved;
- }
- }
-
public bool OnPressed(GlobalAction action)
{
if (beatmap.Disabled)
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 75c8db1612..94080f5592 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.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 System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -30,6 +31,9 @@ namespace osu.Game.Overlays.Settings.Sections
[Resolved]
private SkinManager skins { get; set; }
+ private IBindable> managerAdded;
+ private IBindable> managerRemoved;
+
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
@@ -66,8 +70,11 @@ namespace osu.Game.Overlays.Settings.Sections
},
};
- skins.ItemAdded += itemAdded;
- skins.ItemRemoved += itemRemoved;
+ managerAdded = skins.ItemAdded.GetBoundCopy();
+ managerAdded.BindValueChanged(itemAdded);
+
+ managerRemoved = skins.ItemRemoved.GetBoundCopy();
+ managerRemoved.BindValueChanged(itemRemoved);
config.BindWith(OsuSetting.Skin, configBindable);
@@ -82,19 +89,16 @@ namespace osu.Game.Overlays.Settings.Sections
dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID);
}
- private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray());
-
- private void itemAdded(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(s).ToArray());
-
- protected override void Dispose(bool isDisposing)
+ private void itemAdded(ValueChangedEvent> weakItem)
{
- base.Dispose(isDisposing);
+ if (weakItem.NewValue.TryGetTarget(out var item))
+ Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(item).ToArray());
+ }
- if (skins != null)
- {
- skins.ItemAdded -= itemAdded;
- skins.ItemRemoved -= itemRemoved;
- }
+ private void itemRemoved(ValueChangedEvent> weakItem)
+ {
+ if (weakItem.NewValue.TryGetTarget(out var item))
+ Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != item.ID).ToArray());
}
private class SizeSlider : OsuSliderBar
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 883288d6d7..67216b019d 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -257,7 +257,8 @@ namespace osu.Game.Rulesets.Edit
{
EditorBeatmap.Add(hitObject);
- adjustableClock.Seek(hitObject.GetEndTime());
+ if (adjustableClock.CurrentTime < hitObject.StartTime)
+ adjustableClock.Seek(hitObject.StartTime);
}
showGridFor(Enumerable.Empty());
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index ba6571fe1a..d594909cda 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -11,13 +11,13 @@ using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Threading;
-using osu.Framework.Audio;
using osu.Game.Audio;
using osu.Game.Rulesets.Judgements;
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
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected SkinnableSound Samples { get; private set; }
- protected virtual IEnumerable GetSamples() => HitObject.Samples;
+ public virtual IEnumerable GetSamples() => HitObject.Samples;
private readonly Lazy> nestedHitObjects = new Lazy>();
public IReadOnlyList NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : (IReadOnlyList)Array.Empty();
@@ -96,8 +96,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
///
protected virtual float SamplePlaybackPosition => 0.5f;
- private readonly BindableDouble balanceAdjust = new BindableDouble();
-
private BindableList samplesBindable;
private Bindable startTimeBindable;
private Bindable userPositionalHitSounds;
@@ -173,7 +171,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
Samples = new SkinnableSound(samples.Select(s => HitObject.SampleControlPoint.ApplyTo(s)));
- Samples.AddAdjustment(AdjustableProperty.Balance, balanceAdjust);
AddInternal(Samples);
}
@@ -352,6 +349,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
}
+ [Resolved(canBeNull: true)]
+ private GameplayClock gameplayClock { get; set; }
+
///
/// Plays all the hit sounds for this .
/// This is invoked automatically when this is hit.
@@ -360,8 +360,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
{
const float balance_adjust_amount = 0.4f;
- balanceAdjust.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0);
- Samples?.Play();
+ if (Samples != null && gameplayClock?.IsSeeking != true)
+ {
+ Samples.Balance.Value = balance_adjust_amount * (userPositionalHitSounds.Value ? SamplePlaybackPosition - 0.5f : 0);
+ Samples.Play();
+ }
}
protected override void Update()
diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
index 3ba28aad45..bc9401a095 100644
--- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
+++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs
@@ -29,14 +29,16 @@ namespace osu.Game.Rulesets.UI
///
internal bool FrameStablePlayback = true;
- [Cached]
- public GameplayClock GameplayClock { get; }
+ public GameplayClock GameplayClock => stabilityGameplayClock;
+
+ [Cached(typeof(GameplayClock))]
+ private readonly StabilityGameplayClock stabilityGameplayClock;
public FrameStabilityContainer(double gameplayStartTime = double.MinValue)
{
RelativeSizeAxes = Axes.Both;
- GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
+ stabilityGameplayClock = new StabilityGameplayClock(framedClock = new FramedClock(manualClock = new ManualClock()));
this.gameplayStartTime = gameplayStartTime;
}
@@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.UI
{
if (clock != null)
{
- parentGameplayClock = clock;
+ stabilityGameplayClock.ParentGameplayClock = parentGameplayClock = clock;
GameplayClock.IsPaused.BindTo(clock.IsPaused);
}
}
@@ -187,5 +189,17 @@ namespace osu.Game.Rulesets.UI
}
public ReplayInputHandler ReplayInputHandler { get; set; }
+
+ private class StabilityGameplayClock : GameplayClock
+ {
+ public IFrameBasedClock ParentGameplayClock;
+
+ public StabilityGameplayClock(FramedClock underlyingClock)
+ : base(underlyingClock)
+ {
+ }
+
+ public override bool IsSeeking => ParentGameplayClock != null && Math.Abs(CurrentTime - ParentGameplayClock.CurrentTime) > 200;
+ }
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
index 41f9ebdb82..0052c877f6 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
@@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
{
- double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength);
- return adjustedTime - timeRange - 1000;
+ return TimeAt(-(scrollLength + offset), originTime, timeRange, scrollLength);
}
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
index 5d68de9ce6..54c4f8f7c7 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
@@ -133,7 +133,6 @@ namespace osu.Game.Screens.Multi.Match.Components
{
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
- OnCommit = (sender, text) => apply(),
},
},
new Section("Duration")
@@ -196,7 +195,6 @@ namespace osu.Game.Screens.Multi.Match.Components
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
- OnCommit = (sender, text) => apply()
},
},
new Section("Password (optional)")
@@ -207,7 +205,6 @@ namespace osu.Game.Screens.Multi.Match.Components
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
ReadOnly = true,
- OnCommit = (sender, text) => apply()
},
},
},
@@ -331,6 +328,9 @@ namespace osu.Game.Screens.Multi.Match.Components
private void apply()
{
+ if (!ApplyButton.Enabled.Value)
+ return;
+
hideError();
RoomName.Value = NameField.Text;
diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs
index 8f484d3672..4420b2d58a 100644
--- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs
+++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs
@@ -32,11 +32,16 @@ namespace osu.Game.Screens.Multi.Match.Components
Text = "Start";
}
+ private IBindable> managerAdded;
+ private IBindable> managerRemoved;
+
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
- beatmaps.ItemAdded += beatmapAdded;
- beatmaps.ItemRemoved += beatmapRemoved;
+ managerAdded = beatmaps.ItemAdded.GetBoundCopy();
+ managerAdded.BindValueChanged(beatmapAdded);
+ managerRemoved = beatmaps.ItemRemoved.GetBoundCopy();
+ managerRemoved.BindValueChanged(beatmapRemoved);
SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true);
@@ -56,24 +61,30 @@ namespace osu.Game.Screens.Multi.Match.Components
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null;
}
- private void beatmapAdded(BeatmapSetInfo model)
+ private void beatmapAdded(ValueChangedEvent> weakSet)
{
- int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
- if (beatmapId == null)
- return;
+ if (weakSet.NewValue.TryGetTarget(out var set))
+ {
+ int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
+ if (beatmapId == null)
+ return;
- if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
- Schedule(() => hasBeatmap = true);
+ if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
+ Schedule(() => hasBeatmap = true);
+ }
}
- private void beatmapRemoved(BeatmapSetInfo model)
+ private void beatmapRemoved(ValueChangedEvent> weakSet)
{
- int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
- if (beatmapId == null)
- return;
+ if (weakSet.NewValue.TryGetTarget(out var set))
+ {
+ int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
+ if (beatmapId == null)
+ return;
- if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
- Schedule(() => hasBeatmap = false);
+ if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
+ Schedule(() => hasBeatmap = false);
+ }
}
protected override void Update()
@@ -95,16 +106,5 @@ namespace osu.Game.Screens.Multi.Match.Components
Enabled.Value = hasBeatmap && hasEnoughTime;
}
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (beatmaps != null)
- {
- beatmaps.ItemAdded -= beatmapAdded;
- beatmaps.ItemRemoved -= beatmapRemoved;
- }
- }
}
}
diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
index eef53126c0..caa547ac72 100644
--- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
+++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
@@ -50,6 +50,8 @@ namespace osu.Game.Screens.Multi.Match
private LeaderboardChatDisplay leaderboardChatDisplay;
private MatchSettingsOverlay settingsOverlay;
+ private IBindable> managerAdded;
+
public MatchSubScreen(Room room)
{
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
@@ -181,7 +183,8 @@ namespace osu.Game.Screens.Multi.Match
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
SelectedItem.Value = playlist.FirstOrDefault();
- beatmapManager.ItemAdded += beatmapAdded;
+ managerAdded = beatmapManager.ItemAdded.GetBoundCopy();
+ managerAdded.BindValueChanged(beatmapAdded);
}
public override bool OnExiting(IScreen next)
@@ -214,13 +217,16 @@ namespace osu.Game.Screens.Multi.Match
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
}
- private void beatmapAdded(BeatmapSetInfo model) => Schedule(() =>
+ private void beatmapAdded(ValueChangedEvent> weakSet)
{
- if (Beatmap.Value != beatmapManager.DefaultBeatmap)
- return;
+ Schedule(() =>
+ {
+ if (Beatmap.Value != beatmapManager.DefaultBeatmap)
+ return;
- updateWorkingBeatmap();
- });
+ updateWorkingBeatmap();
+ });
+ }
private void onStart()
{
@@ -235,13 +241,5 @@ namespace osu.Game.Screens.Multi.Match
break;
}
}
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (beatmapManager != null)
- beatmapManager.ItemAdded -= beatmapAdded;
- }
}
}
diff --git a/osu.Game/Screens/Play/DimmableStoryboard.cs b/osu.Game/Screens/Play/DimmableStoryboard.cs
index eabdee95fb..58eb95b7c6 100644
--- a/osu.Game/Screens/Play/DimmableStoryboard.cs
+++ b/osu.Game/Screens/Play/DimmableStoryboard.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
@@ -13,6 +14,8 @@ namespace osu.Game.Screens.Play
///
public class DimmableStoryboard : UserDimContainer
{
+ public Container OverlayLayerContainer { get; private set; }
+
private readonly Storyboard storyboard;
private DrawableStoryboard drawableStoryboard;
@@ -24,6 +27,8 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader]
private void load()
{
+ Add(OverlayLayerContainer = new Container());
+
initializeStoryboard(false);
}
@@ -46,9 +51,15 @@ namespace osu.Game.Screens.Play
drawableStoryboard = storyboard.CreateDrawable();
if (async)
- LoadComponentAsync(drawableStoryboard, Add);
+ LoadComponentAsync(drawableStoryboard, onStoryboardCreated);
else
- Add(drawableStoryboard);
+ onStoryboardCreated(drawableStoryboard);
+ }
+
+ private void onStoryboardCreated(DrawableStoryboard storyboard)
+ {
+ Add(storyboard);
+ OverlayLayerContainer.Add(storyboard.OverlayLayer.CreateProxy());
}
}
}
diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs
index d5f75f6ad1..4f2cf5005c 100644
--- a/osu.Game/Screens/Play/GameplayClock.cs
+++ b/osu.Game/Screens/Play/GameplayClock.cs
@@ -31,6 +31,11 @@ namespace osu.Game.Screens.Play
public bool IsRunning => underlyingClock.IsRunning;
+ ///
+ /// Whether an ongoing seek operation is active.
+ ///
+ public virtual bool IsSeeking => false;
+
public void ProcessFrame()
{
// we do not want to process the underlying clock.
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 1ec3a69b24..3d4b20bec4 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -83,6 +83,8 @@ namespace osu.Game.Screens.Play
private BreakTracker breakTracker;
+ private SkipOverlay skipOverlay;
+
protected ScoreProcessor ScoreProcessor { get; private set; }
protected HealthProcessor HealthProcessor { get; private set; }
@@ -189,6 +191,7 @@ namespace osu.Game.Screens.Play
HUDOverlay.ShowHud.Value = false;
HUDOverlay.ShowHud.Disabled = true;
BreakOverlay.Hide();
+ skipOverlay.Hide();
}
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
@@ -264,6 +267,7 @@ namespace osu.Game.Screens.Play
{
target.AddRange(new[]
{
+ DimmableStoryboard.OverlayLayerContainer.CreateProxy(),
BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor)
{
Clock = DrawableRuleset.FrameStableClock,
@@ -290,7 +294,7 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
- new SkipOverlay(DrawableRuleset.GameplayStartTime)
+ skipOverlay = new SkipOverlay(DrawableRuleset.GameplayStartTime)
{
RequestSkip = GameplayClockContainer.Skip
},
diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs
index ac7e509c2c..b123757ded 100644
--- a/osu.Game/Screens/Play/SkipOverlay.cs
+++ b/osu.Game/Screens/Play/SkipOverlay.cs
@@ -24,13 +24,14 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Screens.Play
{
- public class SkipOverlay : VisibilityContainer, IKeyBindingHandler
+ public class SkipOverlay : CompositeDrawable, IKeyBindingHandler
{
private readonly double startTime;
public Action RequestSkip;
private Button button;
+ private ButtonContainer buttonContainer;
private Box remainingTimeBox;
private FadeContainer fadeContainer;
@@ -61,9 +62,10 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours)
{
- Children = new Drawable[]
+ InternalChild = buttonContainer = new ButtonContainer
{
- fadeContainer = new FadeContainer
+ RelativeSizeAxes = Axes.Both,
+ Child = fadeContainer = new FadeContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
@@ -104,14 +106,8 @@ namespace osu.Game.Screens.Play
button.Action = () => RequestSkip?.Invoke();
displayTime = gameplayClock.CurrentTime;
-
- Show();
}
- protected override void PopIn() => this.FadeIn(fade_time);
-
- protected override void PopOut() => this.FadeOut(fade_time);
-
protected override void Update()
{
base.Update();
@@ -121,13 +117,14 @@ namespace osu.Game.Screens.Play
remainingTimeBox.Width = (float)Interpolation.Lerp(remainingTimeBox.Width, progress, Math.Clamp(Time.Elapsed / 40, 0, 1));
button.Enabled.Value = progress > 0;
- State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden;
+ buttonContainer.State.Value = progress > 0 ? Visibility.Visible : Visibility.Hidden;
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!e.HasAnyButtonPressed)
fadeContainer.Show();
+
return base.OnMouseMove(e);
}
@@ -214,6 +211,13 @@ namespace osu.Game.Screens.Play
public override void Show() => State = Visibility.Visible;
}
+ private class ButtonContainer : VisibilityContainer
+ {
+ protected override void PopIn() => this.FadeIn(fade_time);
+
+ protected override void PopOut() => this.FadeOut(fade_time);
+ }
+
private class Button : OsuClickableContainer
{
private Color4 colourNormal;
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 96b779cd20..f23e1b1ef2 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -131,6 +131,11 @@ namespace osu.Game.Screens.Select
private CarouselRoot root;
+ private IBindable> itemAdded;
+ private IBindable> itemRemoved;
+ private IBindable> itemHidden;
+ private IBindable> itemRestored;
+
public BeatmapCarousel()
{
root = new CarouselRoot(this);
@@ -161,10 +166,14 @@ namespace osu.Game.Screens.Select
RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue;
RightClickScrollingEnabled.TriggerChange();
- beatmaps.ItemAdded += beatmapAdded;
- beatmaps.ItemRemoved += beatmapRemoved;
- beatmaps.BeatmapHidden += beatmapHidden;
- beatmaps.BeatmapRestored += beatmapRestored;
+ itemAdded = beatmaps.ItemAdded.GetBoundCopy();
+ itemAdded.BindValueChanged(beatmapAdded);
+ itemRemoved = beatmaps.ItemRemoved.GetBoundCopy();
+ itemRemoved.BindValueChanged(beatmapRemoved);
+ itemHidden = beatmaps.BeatmapHidden.GetBoundCopy();
+ itemHidden.BindValueChanged(beatmapHidden);
+ itemRestored = beatmaps.BeatmapRestored.GetBoundCopy();
+ itemRestored.BindValueChanged(beatmapRestored);
loadBeatmapSets(GetLoadableBeatmaps());
}
@@ -562,26 +571,34 @@ namespace osu.Game.Screens.Select
{
base.Dispose(isDisposing);
- if (beatmaps != null)
- {
- beatmaps.ItemAdded -= beatmapAdded;
- beatmaps.ItemRemoved -= beatmapRemoved;
- beatmaps.BeatmapHidden -= beatmapHidden;
- beatmaps.BeatmapRestored -= beatmapRestored;
- }
-
// aggressively dispose "off-screen" items to reduce GC pressure.
foreach (var i in Items)
i.Dispose();
}
- private void beatmapRemoved(BeatmapSetInfo item) => RemoveBeatmapSet(item);
+ private void beatmapRemoved(ValueChangedEvent> weakItem)
+ {
+ if (weakItem.NewValue.TryGetTarget(out var item))
+ RemoveBeatmapSet(item);
+ }
- private void beatmapAdded(BeatmapSetInfo item) => UpdateBeatmapSet(item);
+ private void beatmapAdded(ValueChangedEvent> weakItem)
+ {
+ if (weakItem.NewValue.TryGetTarget(out var item))
+ UpdateBeatmapSet(item);
+ }
- private void beatmapRestored(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
+ private void beatmapRestored(ValueChangedEvent> weakItem)
+ {
+ if (weakItem.NewValue.TryGetTarget(out var b))
+ UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
+ }
- private void beatmapHidden(BeatmapInfo b) => UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
+ private void beatmapHidden(ValueChangedEvent> weakItem)
+ {
+ if (weakItem.NewValue.TryGetTarget(out var b))
+ UpdateBeatmapSet(beatmaps.QueryBeatmapSet(s => s.ID == b.BeatmapSetInfoID));
+ }
private CarouselBeatmapSet createCarouselSet(BeatmapSetInfo beatmapSet)
{
diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
index e981550c84..aed25787b0 100644
--- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs
+++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.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 System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -27,6 +28,9 @@ namespace osu.Game.Screens.Select.Carousel
[Resolved]
private IAPIProvider api { get; set; }
+ private IBindable> itemAdded;
+ private IBindable> itemRemoved;
+
public TopLocalRank(BeatmapInfo beatmap)
: base(null)
{
@@ -36,17 +40,24 @@ namespace osu.Game.Screens.Select.Carousel
[BackgroundDependencyLoader]
private void load()
{
- scores.ItemAdded += scoreChanged;
- scores.ItemRemoved += scoreChanged;
+ itemAdded = scores.ItemAdded.GetBoundCopy();
+ itemAdded.BindValueChanged(scoreChanged);
+
+ itemRemoved = scores.ItemRemoved.GetBoundCopy();
+ itemRemoved.BindValueChanged(scoreChanged);
+
ruleset.ValueChanged += _ => fetchAndLoadTopScore();
fetchAndLoadTopScore();
}
- private void scoreChanged(ScoreInfo score)
+ private void scoreChanged(ValueChangedEvent> weakScore)
{
- if (score.BeatmapInfoID == beatmap.ID)
- fetchAndLoadTopScore();
+ if (weakScore.NewValue.TryGetTarget(out var score))
+ {
+ if (score.BeatmapInfoID == beatmap.ID)
+ fetchAndLoadTopScore();
+ }
}
private ScheduledDelegate scheduledRankUpdate;
@@ -75,16 +86,5 @@ namespace osu.Game.Screens.Select.Carousel
.OrderByDescending(s => s.TotalScore)
.FirstOrDefault();
}
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (scores != null)
- {
- scores.ItemAdded -= scoreChanged;
- scores.ItemRemoved -= scoreChanged;
- }
- }
}
}
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index e36493c82f..8e85eb4eb2 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -60,6 +60,8 @@ namespace osu.Game.Screens.Select.Leaderboards
private UserTopScoreContainer topScoreContainer;
+ private IBindable> itemRemoved;
+
///
/// Whether to apply the game's currently selected mods as a filter when retrieving scores.
///
@@ -104,7 +106,8 @@ namespace osu.Game.Screens.Select.Leaderboards
ScoreSelected = s => ScoreSelected?.Invoke(s)
});
- scoreManager.ItemRemoved += onScoreRemoved;
+ itemRemoved = scoreManager.ItemRemoved.GetBoundCopy();
+ itemRemoved.BindValueChanged(onScoreRemoved);
}
protected override void Reset()
@@ -113,7 +116,7 @@ namespace osu.Game.Screens.Select.Leaderboards
TopScore = null;
}
- private void onScoreRemoved(ScoreInfo score) => Schedule(RefreshScores);
+ private void onScoreRemoved(ValueChangedEvent> score) => Schedule(RefreshScores);
protected override bool IsOnlineScope => Scope != BeatmapLeaderboardScope.Local;
@@ -190,13 +193,5 @@ namespace osu.Game.Screens.Select.Leaderboards
{
Action = () => ScoreSelected?.Invoke(model)
};
-
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (scoreManager != null)
- scoreManager.ItemRemoved -= onScoreRemoved;
- }
}
}
diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs
index 3d469ab6e1..d65c74ef62 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -43,12 +43,15 @@ namespace osu.Game.Skinning
this.audio = audio;
this.legacyDefaultResources = legacyDefaultResources;
- ItemRemoved += removedInfo =>
+ ItemRemoved.BindValueChanged(weakRemovedInfo =>
{
- // check the removed skin is not the current user choice. if it is, switch back to default.
- if (removedInfo.ID == CurrentSkinInfo.Value.ID)
- CurrentSkinInfo.Value = SkinInfo.Default;
- };
+ if (weakRemovedInfo.NewValue.TryGetTarget(out var removedInfo))
+ {
+ // check the removed skin is not the current user choice. if it is, switch back to default.
+ if (removedInfo.ID == CurrentSkinInfo.Value.ID)
+ CurrentSkinInfo.Value = SkinInfo.Default;
+ }
+ });
CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = GetSkin(skin.NewValue);
CurrentSkin.ValueChanged += skin =>
diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs
index a78c04ecd4..30320c89a6 100644
--- a/osu.Game/Skinning/SkinnableSound.cs
+++ b/osu.Game/Skinning/SkinnableSound.cs
@@ -4,11 +4,11 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
-using osu.Framework.Audio;
-using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics.Audio;
+using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
namespace osu.Game.Skinning
@@ -17,25 +17,32 @@ namespace osu.Game.Skinning
{
private readonly ISampleInfo[] hitSamples;
- private List<(AdjustableProperty property, BindableDouble bindable)> adjustments;
-
- private SampleChannel[] channels;
-
[Resolved]
private ISampleStore samples { get; set; }
+ public SkinnableSound(ISampleInfo hitSamples)
+ : this(new[] { hitSamples })
+ {
+ }
+
public SkinnableSound(IEnumerable hitSamples)
{
this.hitSamples = hitSamples.ToArray();
- }
-
- public SkinnableSound(ISampleInfo hitSamples)
- {
- this.hitSamples = new[] { hitSamples };
+ InternalChild = samplesContainer = new AudioContainer();
}
private bool looping;
+ private readonly AudioContainer samplesContainer;
+
+ public BindableNumber Volume => samplesContainer.Volume;
+
+ public BindableNumber Balance => samplesContainer.Balance;
+
+ public BindableNumber Frequency => samplesContainer.Frequency;
+
+ public BindableNumber Tempo => samplesContainer.Tempo;
+
public bool Looping
{
get => looping;
@@ -45,33 +52,23 @@ namespace osu.Game.Skinning
looping = value;
- channels?.ForEach(c => c.Looping = looping);
+ samplesContainer.ForEach(c => c.Looping = looping);
}
}
- public void Play() => channels?.ForEach(c => c.Play());
-
- public void Stop() => channels?.ForEach(c => c.Stop());
-
- public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
+ public void Play() => samplesContainer.ForEach(c =>
{
- if (adjustments == null) adjustments = new List<(AdjustableProperty, BindableDouble)>();
+ if (c.AggregateVolume.Value > 0)
+ c.Play();
+ });
- adjustments.Add((type, adjustBindable));
- channels?.ForEach(c => c.AddAdjustment(type, adjustBindable));
- }
-
- public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable)
- {
- adjustments?.Remove((type, adjustBindable));
- channels?.ForEach(c => c.RemoveAdjustment(type, adjustBindable));
- }
+ public void Stop() => samplesContainer.ForEach(c => c.Stop());
public override bool IsPresent => Scheduler.HasPendingTasks;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
- channels = hitSamples.Select(s =>
+ var channels = hitSamples.Select(s =>
{
var ch = skin.GetSample(s);
@@ -88,27 +85,12 @@ namespace osu.Game.Skinning
{
ch.Looping = looping;
ch.Volume.Value = s.Volume / 100.0;
-
- if (adjustments != null)
- {
- foreach (var (property, bindable) in adjustments)
- ch.AddAdjustment(property, bindable);
- }
}
return ch;
- }).Where(c => c != null).ToArray();
- }
+ }).Where(c => c != null);
- protected override void Dispose(bool isDisposing)
- {
- base.Dispose(isDisposing);
-
- if (channels != null)
- {
- foreach (var c in channels)
- c.Dispose();
- }
+ samplesContainer.ChildrenEnumerable = channels.Select(c => new DrawableSample(c));
}
}
}
diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
index c4d796e30b..ec461fa095 100644
--- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs
+++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.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.Linq;
using System.Threading;
using osuTK;
using osu.Framework.Allocation;
@@ -72,6 +73,8 @@ namespace osu.Game.Storyboards.Drawables
}
}
+ public DrawableStoryboardLayer OverlayLayer => Children.Single(layer => layer.Name == "Overlay");
+
private void updateLayerVisibility()
{
foreach (var layer in Children)
diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs
index d13c874ee2..b0fb583d62 100644
--- a/osu.Game/Storyboards/Storyboard.cs
+++ b/osu.Game/Storyboards/Storyboard.cs
@@ -19,19 +19,26 @@ namespace osu.Game.Storyboards
public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0);
+ ///
+ /// Depth of the currently front-most storyboard layer, excluding the overlay layer.
+ ///
+ private int minimumLayerDepth;
+
public Storyboard()
{
layers.Add("Video", new StoryboardLayer("Video", 4, false));
layers.Add("Background", new StoryboardLayer("Background", 3));
layers.Add("Fail", new StoryboardLayer("Fail", 2) { VisibleWhenPassing = false, });
layers.Add("Pass", new StoryboardLayer("Pass", 1) { VisibleWhenFailing = false, });
- layers.Add("Foreground", new StoryboardLayer("Foreground", 0));
+ layers.Add("Foreground", new StoryboardLayer("Foreground", minimumLayerDepth = 0));
+
+ layers.Add("Overlay", new StoryboardLayer("Overlay", int.MinValue));
}
public StoryboardLayer GetLayer(string name)
{
if (!layers.TryGetValue(name, out var layer))
- layers[name] = layer = new StoryboardLayer(name, layers.Values.Min(l => l.Depth) - 1);
+ layers[name] = layer = new StoryboardLayer(name, --minimumLayerDepth);
return layer;
}
diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs
index 142bc60deb..1cde7cf67a 100644
--- a/osu.Game/Storyboards/StoryboardLayer.cs
+++ b/osu.Game/Storyboards/StoryboardLayer.cs
@@ -33,6 +33,6 @@ namespace osu.Game.Storyboards
}
public DrawableStoryboardLayer CreateDrawable()
- => new DrawableStoryboardLayer(this) { Depth = Depth, };
+ => new DrawableStoryboardLayer(this) { Depth = Depth, Name = Name };
}
}
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index b60add6e3b..6ada632850 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -99,10 +99,7 @@ namespace osu.Game.Tests.Beatmaps
private ConvertResult convert(string name, Mod[] mods)
{
- var beatmap = getBeatmap(name);
-
- var rulesetInstance = CreateRuleset();
- beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
+ var beatmap = GetBeatmap(name);
var converterResult = new Dictionary>();
@@ -115,7 +112,7 @@ namespace osu.Game.Tests.Beatmaps
}
};
- working.GetPlayableBeatmap(rulesetInstance.RulesetInfo, mods);
+ working.GetPlayableBeatmap(CreateRuleset().RulesetInfo, mods);
return new ConvertResult
{
@@ -143,14 +140,19 @@ namespace osu.Game.Tests.Beatmaps
}
}
- private IBeatmap getBeatmap(string name)
+ public IBeatmap GetBeatmap(string name)
{
using (var resStream = openResource($"{resource_namespace}.{name}.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var decoder = Decoder.GetDecoder(stream);
((LegacyBeatmapDecoder)decoder).ApplyOffsets = false;
- return decoder.Decode(stream);
+ var beatmap = decoder.Decode(stream);
+
+ var rulesetInstance = CreateRuleset();
+ beatmap.BeatmapInfo.Ruleset = beatmap.BeatmapInfo.RulesetID == rulesetInstance.RulesetInfo.ID ? rulesetInstance.RulesetInfo : new RulesetInfo();
+
+ return beatmap;
}
}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ee6206e166..9112dfe46e 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 cbf8600c62..3f0630af5f 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -80,7 +80,7 @@
-
+