diff --git a/global.json b/global.json
index 233a040d18..a9a531f59c 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.0.52"
+ "Microsoft.Build.Traversal": "2.1.1"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 1a76a24496..62397ca028 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index 8c48158acd..466cbdaf8d 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -14,6 +14,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
+ [Timeout(10000)]
public class CatchBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index f9d56dfa78..dfe3bf8af4 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
index 18cc300ff9..f009c10a9c 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
@@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
Name = @"Fruit Count",
Content = fruits.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
},
new BeatmapStatistic
{
Name = @"Juice Stream Count",
Content = juiceStreams.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
},
new BeatmapStatistic
{
Name = @"Banana Shower Count",
Content = bananaShowers.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
}
};
}
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index 9437023c70..ca75a816f1 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -21,13 +21,11 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using System;
-using osu.Framework.Testing;
using osu.Game.Rulesets.Catch.Skinning;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
{
- [ExcludeFromDynamicCompile]
public class CatchRuleset : Ruleset, ILegacyRuleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
index d929da1a29..ea2f031d65 100644
--- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Skinning;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.Skinning
{
@@ -61,7 +62,12 @@ namespace osu.Game.Rulesets.Catch.Skinning
switch (lookup)
{
case CatchSkinColour colour:
- return Source.GetConfig(new SkinCustomColourLookup(colour));
+ var result = (Bindable)Source.GetConfig(new SkinCustomColourLookup(colour));
+ if (result == null)
+ return null;
+
+ result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value);
+ return (IBindable)result;
}
return Source.GetConfig(lookup);
diff --git a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
index 5be54d3882..381d066750 100644
--- a/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/LegacyFruitPiece.cs
@@ -40,7 +40,6 @@ namespace osu.Game.Rulesets.Catch.Skinning
colouredSprite = new Sprite
{
Texture = skin.GetTexture(lookupName),
- Colour = drawableObject.AccentColour.Value,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@@ -76,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Skinning
{
base.LoadComplete();
- accentColour.BindValueChanged(colour => colouredSprite.Colour = colour.NewValue, true);
+ accentColour.BindValueChanged(colour => colouredSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
}
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
index d0ff1fab43..0c57267970 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs
@@ -14,11 +14,13 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
+ [Timeout(10000)]
public class ManiaBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
[TestCase("basic")]
+ [TestCase("zero-length-slider")]
public void Test(string name) => base.Test(name);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
index 957743c5f1..b22687a0a7 100644
--- a/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
+++ b/osu.Game.Rulesets.Mania.Tests/ManiaLegacyModConversionTest.cs
@@ -23,6 +23,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(ManiaModPerfect) })]
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(ManiaModDoubleTime), typeof(ManiaModPerfect) })]
[TestCase(LegacyMods.Random | LegacyMods.SuddenDeath, new[] { typeof(ManiaModRandom), typeof(ManiaModSuddenDeath) })]
+ [TestCase(LegacyMods.Flashlight | LegacyMods.Mirror, new[] { typeof(ManiaModFlashlight), typeof(ManiaModMirror) })]
public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
protected override Ruleset CreateRuleset() => new ManiaRuleset();
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
index ff4865c71d..8ba58e3af3 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ColumnTestContainer.cs
@@ -22,18 +22,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
[Cached]
private readonly Column column;
- public ColumnTestContainer(int column, ManiaAction action)
+ public ColumnTestContainer(int column, ManiaAction action, bool showColumn = false)
{
- this.column = new Column(column)
+ InternalChildren = new[]
{
- Action = { Value = action },
- AccentColour = Color4.Orange,
- ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd
- };
-
- InternalChild = content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
- {
- RelativeSizeAxes = Axes.Both
+ this.column = new Column(column)
+ {
+ Action = { Value = action },
+ AccentColour = Color4.Orange,
+ ColumnType = column % 2 == 0 ? ColumnType.Even : ColumnType.Odd,
+ Alpha = showColumn ? 1 : 0
+ },
+ content = new ManiaInputManager(new ManiaRuleset().RulesetInfo, 4)
+ {
+ RelativeSizeAxes = Axes.Both
+ },
+ this.column.TopLevelContainer.CreateProxy()
};
}
}
diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
index 18eebada00..d24c81dac6 100644
--- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
+++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaHitObjectTestScene.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
- new ColumnTestContainer(0, ManiaAction.Key1)
+ new ColumnTestContainer(0, ManiaAction.Key1, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
}));
})
},
- new ColumnTestContainer(1, ManiaAction.Key2)
+ new ColumnTestContainer(1, ManiaAction.Key2, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
new file mode 100644
index 0000000000..ab840e1c46
--- /dev/null
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
@@ -0,0 +1,117 @@
+// 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 NUnit.Framework;
+using osu.Framework.Extensions.TypeExtensions;
+using osu.Framework.Screens;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects;
+using osu.Game.Rulesets.Mania.Replays;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Mania.Tests
+{
+ public class TestSceneOutOfOrderHits : RateAdjustedBeatmapTestScene
+ {
+ [Test]
+ public void TestPreviousHitWindowDoesNotExtendPastNextObject()
+ {
+ var objects = new List();
+ var frames = new List();
+
+ for (int i = 0; i < 7; i++)
+ {
+ double time = 1000 + i * 100;
+
+ objects.Add(new Note { StartTime = time });
+
+ if (i > 0)
+ {
+ frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1));
+ frames.Add(new ManiaReplayFrame(time + 11));
+ }
+ }
+
+ performTest(objects, frames);
+
+ addJudgementAssert(objects[0], HitResult.Miss);
+
+ for (int i = 1; i < 7; i++)
+ {
+ addJudgementAssert(objects[i], HitResult.Perfect);
+ addJudgementOffsetAssert(objects[i], 10);
+ }
+ }
+
+ private void addJudgementAssert(ManiaHitObject hitObject, HitResult result)
+ {
+ AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judgement is {result}",
+ () => judgementResults.Single(r => r.HitObject == hitObject).Type == result);
+ }
+
+ private void addJudgementOffsetAssert(ManiaHitObject hitObject, double offset)
+ {
+ AddAssert($"({hitObject.GetType().ReadableName()} @ {hitObject.StartTime}) judged at {offset}",
+ () => Precision.AlmostEquals(judgementResults.Single(r => r.HitObject == hitObject).TimeOffset, offset, 100));
+ }
+
+ private ScoreAccessibleReplayPlayer currentPlayer;
+ private List judgementResults;
+
+ private void performTest(List hitObjects, List frames)
+ {
+ AddStep("load player", () =>
+ {
+ Beatmap.Value = CreateWorkingBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 })
+ {
+ HitObjects = hitObjects,
+ BeatmapInfo =
+ {
+ Ruleset = new ManiaRuleset().RulesetInfo
+ },
+ });
+
+ Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
+
+ var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
+
+ p.OnLoadComplete += _ =>
+ {
+ p.ScoreProcessor.NewJudgement += result =>
+ {
+ if (currentPlayer == p) judgementResults.Add(result);
+ };
+ };
+
+ LoadScreen(currentPlayer = p);
+ judgementResults = new List();
+ });
+
+ AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
+ AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
+ AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
+ }
+
+ private class ScoreAccessibleReplayPlayer : ReplayPlayer
+ {
+ public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
+
+ protected override bool PauseOnFocusLost => false;
+
+ public ScoreAccessibleReplayPlayer(Score score)
+ : base(score, false, false)
+ {
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index ed00ed0b4c..892f27d27f 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
index dc24a344e9..d1d5adea75 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI;
@@ -41,14 +40,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
new BeatmapStatistic
{
Name = @"Note Count",
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
Content = notes.ToString(),
- Icon = FontAwesome.Regular.Circle
},
new BeatmapStatistic
{
Name = @"Hold Note Count",
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
Content = holdnotes.ToString(),
- Icon = FontAwesome.Regular.Circle
},
};
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index b025ac7992..211905835c 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -5,7 +5,6 @@ using osu.Game.Rulesets.Mania.Objects;
using System;
using System.Linq;
using System.Collections.Generic;
-using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
@@ -167,8 +166,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
var positionData = original as IHasPosition;
- for (double time = original.StartTime; !Precision.DefinitelyBigger(time, generator.EndTime); time += generator.SegmentDuration)
+ for (int i = 0; i <= generator.SpanCount; i++)
{
+ double time = original.StartTime + generator.SegmentDuration * i;
+
recordNote(time, positionData?.Position ?? Vector2.Zero);
computeDensity(time);
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index d03eb0b3c9..fe146c5324 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -27,8 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public readonly double EndTime;
public readonly double SegmentDuration;
-
- private readonly int spanCount;
+ public readonly int SpanCount;
private PatternType convertType;
@@ -42,20 +41,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var distanceData = hitObject as IHasDistance;
var repeatsData = hitObject as IHasRepeats;
- spanCount = repeatsData?.SpanCount() ?? 1;
+ SpanCount = repeatsData?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats
- double distance = (distanceData?.Distance ?? 0) * spanCount;
+ double distance = (distanceData?.Distance ?? 0) * SpanCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
EndTime = hitObject.StartTime + osuDuration;
- SegmentDuration = (EndTime - HitObject.StartTime) / spanCount;
+ SegmentDuration = (EndTime - HitObject.StartTime) / SpanCount;
}
public override IEnumerable Generate()
@@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
return pattern;
}
- if (spanCount > 1)
+ if (SpanCount > 1)
{
if (SegmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1);
@@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (SegmentDuration <= 120)
{
convertType |= PatternType.ForceNotStack;
- return generateRandomNotes(HitObject.StartTime, spanCount + 1);
+ return generateRandomNotes(HitObject.StartTime, SpanCount + 1);
}
if (SegmentDuration <= 160)
@@ -117,7 +116,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
- if (SegmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
+ if (SegmentDuration > 400 && SpanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime);
@@ -251,7 +250,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
bool increasing = Random.NextDouble() > 0.5;
- for (int i = 0; i <= spanCount; i++)
+ for (int i = 0; i <= SpanCount; i++)
{
addToPattern(pattern, column, startTime, startTime);
startTime += SegmentDuration;
@@ -302,7 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
- for (int i = 0; i <= spanCount; i++)
+ for (int i = 0; i <= SpanCount; i++)
{
addToPattern(pattern, nextColumn, startTime, startTime);
@@ -393,7 +392,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
- int columnRepeat = Math.Min(spanCount, TotalColumns);
+ int columnRepeat = Math.Min(SpanCount, TotalColumns);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if (convertType.HasFlag(PatternType.ForceNotStack) && PreviousPattern.ColumnWithObjects < TotalColumns)
@@ -447,7 +446,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var rowPattern = new Pattern();
- for (int i = 0; i <= spanCount; i++)
+ for (int i = 0; i <= SpanCount; i++)
{
if (!(ignoreHead && startTime == HitObject.StartTime))
{
diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
index 2795868c97..71ac85dd1b 100644
--- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs
@@ -12,7 +12,6 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
-using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
@@ -35,7 +34,6 @@ using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets.Mania
{
- [ExcludeFromDynamicCompile]
public class ManiaRuleset : Ruleset, ILegacyRuleset
{
///
@@ -126,6 +124,9 @@ namespace osu.Game.Rulesets.Mania
if (mods.HasFlag(LegacyMods.Random))
yield return new ManiaModRandom();
+
+ if (mods.HasFlag(LegacyMods.Mirror))
+ yield return new ManiaModMirror();
}
public override LegacyMods ConvertToLegacyMods(Mod[] mods)
@@ -175,6 +176,10 @@ namespace osu.Game.Rulesets.Mania
case ManiaModFadeIn _:
value |= LegacyMods.FadeIn;
break;
+
+ case ManiaModMirror _:
+ value |= LegacyMods.Mirror;
+ break;
}
}
@@ -326,6 +331,16 @@ namespace osu.Game.Rulesets.Mania
Height = 250
}),
}
+ },
+ new StatisticRow
+ {
+ Columns = new[]
+ {
+ new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
+ {
+ new UnstableRate(score.HitEvents)
+ }))
+ }
}
};
}
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
index 0712026ca6..2ebcc5451a 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs
@@ -238,7 +238,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (Tail.AllJudged)
+ {
ApplyResult(r => r.Type = HitResult.Perfect);
+ endHold();
+ }
if (Tail.Result.Type == HitResult.Miss)
HasBroken = true;
@@ -252,6 +255,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value)
return false;
+ if (CheckHittable?.Invoke(this, Time.Current) == false)
+ return false;
+
// The tail has a lenience applied to it which is factored into the miss window (i.e. the miss judgement will be delayed).
// But the hold cannot ever be started within the late-lenience window, so we should skip trying to begin the hold during that time.
// Note: Unlike below, we use the tail's start time to determine the time offset.
diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
index ab76a5b8f8..08c41b0d75 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.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 JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -8,6 +9,7 @@ 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
{
@@ -34,6 +36,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
+ ///
+ /// Whether this can be hit, given a time value.
+ /// If non-null, judgements will be ignored whilst the function returns false.
+ ///
+ public Func CheckHittable;
+
protected DrawableManiaHitObject(ManiaHitObject hitObject)
: base(hitObject)
{
@@ -124,6 +132,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
break;
}
}
+
+ ///
+ /// Causes this to get missed, disregarding all conditions in implementations of .
+ ///
+ public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
}
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 9451bc4430..973dc06e05 100644
--- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs
@@ -64,6 +64,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != Action.Value)
return false;
+ if (CheckHittable?.Invoke(this, Time.Current) == false)
+ return false;
+
return UpdateResult(true);
}
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json
new file mode 100644
index 0000000000..229760cd1c
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider-expected-conversion.json
@@ -0,0 +1,14 @@
+{
+ "Mappings": [{
+ "RandomW": 3083084786,
+ "RandomX": 273326509,
+ "RandomY": 273553282,
+ "RandomZ": 2659838971,
+ "StartTime": 4836,
+ "Objects": [{
+ "StartTime": 4836,
+ "EndTime": 4836,
+ "Column": 0
+ }]
+ }]
+}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu
new file mode 100644
index 0000000000..9b8ac1f9db
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/zero-length-slider.osu
@@ -0,0 +1,20 @@
+osu file format v14
+
+[General]
+StackLeniency: 0.7
+Mode: 0
+
+[Difficulty]
+HPDrainRate:1
+CircleSize:4
+OverallDifficulty:1
+ApproachRate:9
+SliderMultiplier:2.5
+SliderTickRate:0.5
+
+[TimingPoints]
+34,431.654676258993,4,1,0,50,1,0
+4782,-66.6666666666667,4,1,0,20,0,0
+
+[HitObjects]
+15,199,4836,22,0,L,1,46.8750017881394
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs b/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.cs
new file mode 100644
index 0000000000..c8b05ed2f8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/Skinning/HitTargetInsetContainer.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 osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.Mania.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+using osu.Game.Skinning;
+
+namespace osu.Game.Rulesets.Mania.Skinning
+{
+ public class HitTargetInsetContainer : Container
+ {
+ private readonly IBindable direction = new Bindable();
+
+ protected override Container Content => content;
+ private readonly Container content;
+
+ private float hitPosition;
+
+ public HitTargetInsetContainer()
+ {
+ RelativeSizeAxes = Axes.Both;
+
+ InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
+ {
+ hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION;
+
+ direction.BindTo(scrollingInfo.Direction);
+ direction.BindValueChanged(onDirectionChanged, true);
+ }
+
+ private void onDirectionChanged(ValueChangedEvent direction)
+ {
+ content.Padding = direction.NewValue == ScrollingDirection.Up
+ ? new MarginPadding { Top = hitPosition }
+ : new MarginPadding { Bottom = hitPosition };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
index 9f716428c0..c0f0fcb4af 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.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;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -19,7 +21,14 @@ namespace osu.Game.Rulesets.Mania.Skinning
private readonly IBindable direction = new Bindable();
private readonly IBindable isHitting = new Bindable();
- private Drawable sprite;
+ [CanBeNull]
+ private Drawable bodySprite;
+
+ [CanBeNull]
+ private Drawable lightContainer;
+
+ [CanBeNull]
+ private Drawable light;
public LegacyBodyPiece()
{
@@ -32,7 +41,39 @@ namespace osu.Game.Rulesets.Mania.Skinning
string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
?? $"mania-note{FallbackColumnIndex}L";
- sprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
+ string lightImage = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightImage)?.Value
+ ?? "lightingL";
+
+ float lightScale = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
+ ?? 1;
+
+ // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
+ // This animation is discarded and re-queried with the appropriate frame length afterwards.
+ var tmp = skin.GetAnimation(lightImage, true, false);
+ double frameLength = 0;
+ if (tmp is IFramedAnimation tmpAnimation && tmpAnimation.FrameCount > 0)
+ frameLength = Math.Max(1000 / 60.0, 170.0 / tmpAnimation.FrameCount);
+
+ light = skin.GetAnimation(lightImage, true, true, frameLength: frameLength).With(d =>
+ {
+ if (d == null)
+ return;
+
+ d.Origin = Anchor.Centre;
+ d.Blending = BlendingParameters.Additive;
+ d.Scale = new Vector2(lightScale);
+ });
+
+ if (light != null)
+ {
+ lightContainer = new HitTargetInsetContainer
+ {
+ Alpha = 0,
+ Child = light
+ };
+ }
+
+ bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
{
if (d == null)
return;
@@ -47,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.Skinning
// Todo: Wrap
});
- if (sprite != null)
- InternalChild = sprite;
+ if (bodySprite != null)
+ InternalChild = bodySprite;
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
@@ -60,28 +101,68 @@ namespace osu.Game.Rulesets.Mania.Skinning
private void onIsHittingChanged(ValueChangedEvent isHitting)
{
- if (!(sprite is TextureAnimation animation))
+ if (bodySprite is TextureAnimation bodyAnimation)
+ {
+ bodyAnimation.GotoFrame(0);
+ bodyAnimation.IsPlaying = isHitting.NewValue;
+ }
+
+ if (lightContainer == null)
return;
- animation.GotoFrame(0);
- animation.IsPlaying = isHitting.NewValue;
+ if (isHitting.NewValue)
+ {
+ // Clear the fade out and, more importantly, the removal.
+ lightContainer.ClearTransforms();
+
+ // Only add the container if the removal has taken place.
+ if (lightContainer.Parent == null)
+ Column.TopLevelContainer.Add(lightContainer);
+
+ // The light must be seeked only after being loaded, otherwise a nullref occurs (https://github.com/ppy/osu-framework/issues/3847).
+ if (light is TextureAnimation lightAnimation)
+ lightAnimation.GotoFrame(0);
+
+ lightContainer.FadeIn(80);
+ }
+ else
+ {
+ lightContainer.FadeOut(120)
+ .OnComplete(d => Column.TopLevelContainer.Remove(d));
+ }
}
private void onDirectionChanged(ValueChangedEvent direction)
{
- if (sprite == null)
- return;
-
if (direction.NewValue == ScrollingDirection.Up)
{
- sprite.Origin = Anchor.BottomCentre;
- sprite.Scale = new Vector2(1, -1);
+ if (bodySprite != null)
+ {
+ bodySprite.Origin = Anchor.BottomCentre;
+ bodySprite.Scale = new Vector2(1, -1);
+ }
+
+ if (light != null)
+ light.Anchor = Anchor.TopCentre;
}
else
{
- sprite.Origin = Anchor.TopCentre;
- sprite.Scale = Vector2.One;
+ if (bodySprite != null)
+ {
+ bodySprite.Origin = Anchor.TopCentre;
+ bodySprite.Scale = Vector2.One;
+ }
+
+ if (light != null)
+ light.Anchor = Anchor.BottomCentre;
}
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ lightContainer?.Expire();
+ }
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
index acae4cd6fb..3bf51b3073 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
- Colour = lightColour,
+ Colour = LegacyColourCompatibility.DisallowZeroAlpha(lightColour),
Texture = skin.GetTexture(lightImage),
RelativeSizeAxes = Axes.X,
Width = 1,
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
index 1e1a9c2237..6eced571d2 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
Anchor = Anchor.CentreLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
- Colour = lineColour,
+ Colour = LegacyColourCompatibility.DisallowZeroAlpha(lineColour),
Alpha = showJudgementLine ? 0.9f : 0
}
}
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
index 44f3e7d7b3..b269ea25d4 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs
@@ -65,6 +65,9 @@ namespace osu.Game.Rulesets.Mania.Skinning
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
+
+ if (GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeysUnderNotes)?.Value ?? false)
+ Column.UnderlayElements.Add(CreateProxy());
}
private void onDirectionChanged(ValueChangedEvent direction)
diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
index 19ec86b1ed..b0bab8e760 100644
--- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
+++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs
@@ -2,14 +2,12 @@
// 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.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
-using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
@@ -108,75 +106,43 @@ namespace osu.Game.Rulesets.Mania.Skinning
InternalChildren = new Drawable[]
{
- new Container
+ LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
{
- RelativeSizeAxes = Axes.Both,
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = backgroundColour
- },
- },
+ RelativeSizeAxes = Axes.Both
+ }, backgroundColour),
new HitTargetInsetContainer
{
RelativeSizeAxes = Axes.Both,
Children = new[]
{
- new Box
+ new Container
{
RelativeSizeAxes = Axes.Y,
Width = leftLineWidth,
Scale = new Vector2(0.740f, 1),
- Colour = lineColour,
- Alpha = hasLeftLine ? 1 : 0
+ Alpha = hasLeftLine ? 1 : 0,
+ Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ }, lineColour)
},
- new Box
+ new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = rightLineWidth,
Scale = new Vector2(0.740f, 1),
- Colour = lineColour,
- Alpha = hasRightLine ? 1 : 0
+ Alpha = hasRightLine ? 1 : 0,
+ Child = LegacyColourCompatibility.ApplyWithDoubledAlpha(new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ }, lineColour)
},
}
}
};
}
}
-
- private class HitTargetInsetContainer : Container
- {
- private readonly IBindable direction = new Bindable();
-
- protected override Container Content => content;
- private readonly Container content;
-
- private float hitPosition;
-
- public HitTargetInsetContainer()
- {
- RelativeSizeAxes = Axes.Both;
-
- InternalChild = content = new Container { RelativeSizeAxes = Axes.Both };
- }
-
- [BackgroundDependencyLoader]
- private void load(ISkinSource skin, IScrollingInfo scrollingInfo)
- {
- hitPosition = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? Stage.HIT_TARGET_POSITION;
-
- direction.BindTo(scrollingInfo.Direction);
- direction.BindValueChanged(onDirectionChanged, true);
- }
-
- private void onDirectionChanged(ValueChangedEvent direction)
- {
- content.Padding = direction.NewValue == ScrollingDirection.Up
- ? new MarginPadding { Top = hitPosition }
- : new MarginPadding { Bottom = hitPosition };
- }
- }
}
}
diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs
index de4648e4fa..9aabcc6699 100644
--- a/osu.Game.Rulesets.Mania/UI/Column.cs
+++ b/osu.Game.Rulesets.Mania/UI/Column.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Skinning;
using osuTK;
using osu.Game.Rulesets.Mania.Beatmaps;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly ColumnHitObjectArea HitObjectArea;
internal readonly Container TopLevelContainer;
private readonly DrawablePool hitExplosionPool;
+ private readonly OrderedHitPolicy hitPolicy;
public Container UnderlayElements => HitObjectArea.UnderlayElements;
@@ -65,6 +67,8 @@ namespace osu.Game.Rulesets.Mania.UI
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
};
+ hitPolicy = new OrderedHitPolicy(HitObjectContainer);
+
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
}
@@ -90,6 +94,9 @@ namespace osu.Game.Rulesets.Mania.UI
hitObject.AccentColour.Value = AccentColour;
hitObject.OnNewResult += OnNewResult;
+ DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
+ maniaObject.CheckHittable = hitPolicy.IsHittable;
+
HitObjectContainer.Add(hitObject);
}
@@ -104,6 +111,9 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
{
+ if (result.IsHit)
+ hitPolicy.HandleHit(judgedObject);
+
if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value)
return;
diff --git a/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs
new file mode 100644
index 0000000000..0f9cd48dd8
--- /dev/null
+++ b/osu.Game.Rulesets.Mania/UI/OrderedHitPolicy.cs
@@ -0,0 +1,78 @@
+// 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.Collections.Generic;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Game.Rulesets.Mania.Objects.Drawables;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Mania.UI
+{
+ ///
+ /// Ensures that only the most recent is hittable, affectionately known as "note lock".
+ ///
+ public class OrderedHitPolicy
+ {
+ private readonly HitObjectContainer hitObjectContainer;
+
+ public OrderedHitPolicy(HitObjectContainer hitObjectContainer)
+ {
+ this.hitObjectContainer = hitObjectContainer;
+ }
+
+ ///
+ /// Determines whether a can be hit at a point in time.
+ ///
+ ///
+ /// Only the most recent can be hit, a previous hitobject's window cannot extend past the next one.
+ ///
+ /// The to check.
+ /// The time to check.
+ /// Whether can be hit at the given .
+ public bool IsHittable(DrawableHitObject hitObject, double time)
+ {
+ var nextObject = hitObjectContainer.AliveObjects.GetNext(hitObject);
+ return nextObject == null || time < nextObject.HitObject.StartTime;
+ }
+
+ ///
+ /// Handles a being hit to potentially miss all earlier s.
+ ///
+ /// The that was hit.
+ public void HandleHit(DrawableHitObject hitObject)
+ {
+ if (!IsHittable(hitObject, hitObject.HitObject.StartTime + hitObject.Result.TimeOffset))
+ throw new InvalidOperationException($"A {hitObject} was hit before it became hittable!");
+
+ foreach (var obj in enumerateHitObjectsUpTo(hitObject.HitObject.StartTime))
+ {
+ if (obj.Judged)
+ continue;
+
+ ((DrawableManiaHitObject)obj).MissForcefully();
+ }
+ }
+
+ private IEnumerable enumerateHitObjectsUpTo(double targetTime)
+ {
+ foreach (var obj in hitObjectContainer.AliveObjects)
+ {
+ if (obj.HitObject.GetEndTime() >= targetTime)
+ yield break;
+
+ yield return obj;
+
+ foreach (var nestedObj in obj.NestedHitObjects)
+ {
+ if (nestedObj.HitObject.GetEndTime() >= targetTime)
+ break;
+
+ yield return nestedObj;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
index cd3daf18a9..7d32895083 100644
--- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs
@@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
+ [Timeout(10000)]
public class OsuBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-background@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-background@2x.png
new file mode 100644
index 0000000000..4f50f638c5
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-background@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-circle@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-circle@2x.png
new file mode 100644
index 0000000000..daf28e09cb
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-circle@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-metre@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-metre@2x.png
new file mode 100644
index 0000000000..6ef1068420
Binary files /dev/null and b/osu.Game.Rulesets.Osu.Tests/Resources/metrics-skin/spinner-metre@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
index a69646507a..3d100e4b1c 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
@@ -22,7 +22,6 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using osu.Game.Storyboards;
using osuTK;
-using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Rulesets.Osu.Tests
{
@@ -32,8 +31,6 @@ namespace osu.Game.Rulesets.Osu.Tests
[Resolved]
private AudioManager audioManager { get; set; }
- private TrackVirtualManual track;
-
protected override bool Autoplay => autoplay;
private bool autoplay;
@@ -44,11 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private const double fade_in_modifier = -1200;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- {
- var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
- track = (TrackVirtualManual)working.Track;
- return working;
- }
+ => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
@@ -72,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep("enable autoplay", () => autoplay = true);
base.SetUpSteps();
- AddUntilStep("wait for track to start running", () => track.IsRunning);
+ AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
double startTime = hitObjects[sliderIndex].StartTime;
retrieveDrawableSlider(sliderIndex);
@@ -97,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
AddStep("have autoplay", () => autoplay = true);
base.SetUpSteps();
- AddUntilStep("wait for track to start running", () => track.IsRunning);
+ AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
double startTime = hitObjects[sliderIndex].StartTime;
retrieveDrawableSlider(sliderIndex);
@@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void addSeekStep(double time)
{
- AddStep($"seek to {time}", () => track.Seek(time));
+ AddStep($"seek to {time}", () => MusicController.SeekTo(time));
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
index 47b3926ceb..94d1cb8864 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs
@@ -9,6 +9,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
@@ -62,7 +63,8 @@ namespace osu.Game.Rulesets.Osu.Tests
drawableSpinner = new TestDrawableSpinner(spinner, auto)
{
Anchor = Anchor.Centre,
- Depth = depthIndex++
+ Depth = depthIndex++,
+ Scale = new Vector2(0.75f)
};
foreach (var mod in SelectedMods.Value.OfType())
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
index 69857f8ef9..f7909071ea 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
@@ -25,7 +25,6 @@ using osu.Game.Scoring;
using osu.Game.Storyboards;
using osu.Game.Tests.Visual;
using osuTK;
-using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Rulesets.Osu.Tests
{
@@ -34,18 +33,12 @@ namespace osu.Game.Rulesets.Osu.Tests
[Resolved]
private AudioManager audioManager { get; set; }
- private TrackVirtualManual track;
-
protected override bool Autoplay => true;
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new ScoreExposedPlayer();
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- {
- var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
- track = (TrackVirtualManual)working.Track;
- return working;
- }
+ => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
private DrawableSpinner drawableSpinner;
private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single();
@@ -55,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
base.SetUpSteps();
- AddUntilStep("wait for track to start running", () => track.IsRunning);
+ AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)Player.DrawableRuleset.Playfield.AllHitObjects.First());
}
@@ -201,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Tests
addSeekStep(0);
- AddStep("adjust track rate", () => track.AddAdjustment(AdjustableProperty.Tempo, new BindableDouble(rate)));
+ 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,
@@ -230,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private void addSeekStep(double time)
{
- AddStep($"seek to {time}", () => track.Seek(time));
+ AddStep($"seek to {time}", () => MusicController.SeekTo(time));
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index f3837ea6b1..3639c3616f 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
index 491d82b89e..2d3cc3c103 100644
--- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
@@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
Name = @"Circle Count",
Content = circles.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
},
new BeatmapStatistic
{
Name = @"Slider Count",
Content = sliders.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
},
new BeatmapStatistic
{
Name = @"Spinner Count",
Content = spinners.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
}
};
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
index 8308c0c576..2946331bc6 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override float SamplePlaybackPosition => HitObject.X / OsuPlayfield.BASE_SIZE.X;
///
- /// Whether this can be hit.
+ /// Whether this can be hit, given a time value.
/// If non-null, judgements will be ignored (resulting in a shake) whilst the function returns false.
///
public Func CheckHittable;
diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs
index eaa5d8937a..7f4a0dcbbb 100644
--- a/osu.Game.Rulesets.Osu/OsuRuleset.cs
+++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs
@@ -30,14 +30,12 @@ using osu.Game.Scoring;
using osu.Game.Skinning;
using System;
using System.Linq;
-using osu.Framework.Testing;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Statistics;
using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets.Osu
{
- [ExcludeFromDynamicCompile]
public class OsuRuleset : Ruleset, ILegacyRuleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods);
@@ -193,30 +191,46 @@ namespace osu.Game.Rulesets.Osu
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
- public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
+ public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
{
- new StatisticRow
+ var timedHitEvents = score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList();
+
+ return new[]
{
- Columns = new[]
+ new StatisticRow
{
- new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList())
+ Columns = new[]
{
- RelativeSizeAxes = Axes.X,
- Height = 250
- }),
- }
- },
- new StatisticRow
- {
- Columns = new[]
+ new StatisticItem("Timing Distribution",
+ new HitEventTimingDistributionGraph(timedHitEvents)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250
+ }),
+ }
+ },
+ new StatisticRow
{
- new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
+ Columns = new[]
{
- RelativeSizeAxes = Axes.X,
- Height = 250
- }),
+ new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250
+ }),
+ }
+ },
+ new StatisticRow
+ {
+ Columns = new[]
+ {
+ new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
+ {
+ new UnstableRate(timedHitEvents)
+ }))
+ }
}
- }
- };
+ };
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index 4cb2cd6539..76b2631894 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -154,8 +154,12 @@ namespace osu.Game.Rulesets.Osu.Replays
// The startPosition for the slider should not be its .Position, but the point on the circle whose tangent crosses the current cursor position
// We also modify spinnerDirection so it spins in the direction it enters the spin circle, to make a smooth transition.
// TODO: Shouldn't the spinner always spin in the same direction?
- if (h is Spinner)
+ if (h is Spinner spinner)
{
+ // spinners with 0 spins required will auto-complete - don't bother
+ if (spinner.SpinsRequired == 0)
+ return;
+
calcSpinnerStartPosAndDirection(((OsuReplayFrame)Frames[^1]).Position, out startPosition, out spinnerDirection);
Vector2 spinCentreOffset = SPINNER_CENTRE - ((OsuReplayFrame)Frames[^1]).Position;
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
index 0ab3e8825b..d15a0a3203 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyMainCirclePiece.cs
@@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
hitCircleSprite = new Sprite
{
Texture = getTextureWithFallback(string.Empty),
- Colour = drawableObject.AccentColour.Value,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
@@ -107,7 +106,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
base.LoadComplete();
state.BindValueChanged(updateState, true);
- accentColour.BindValueChanged(colour => hitCircleSprite.Colour = colour.NewValue, true);
+ accentColour.BindValueChanged(colour => hitCircleSprite.Colour = LegacyColourCompatibility.DisallowZeroAlpha(colour.NewValue), true);
indexInCurrentCombo.BindValueChanged(index => hitCircleText.Text = (index.NewValue + 1).ToString(), true);
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs
index 81a0df5ea5..e157842fd1 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacyOldStyleSpinner.cs
@@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
private DrawableSpinner drawableSpinner;
private Sprite disc;
+ private Sprite metreSprite;
private Container metre;
- private const float background_y_offset = 20;
-
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)
@@ -35,50 +35,58 @@ namespace osu.Game.Rulesets.Osu.Skinning
RelativeSizeAxes = Axes.Both;
- InternalChildren = new Drawable[]
+ InternalChild = new Container
{
- new Sprite
+ // the old-style spinner relied heavily on absolute screen-space coordinate values.
+ // wrap everything in a container simulating absolute coords to preserve alignment
+ // as there are skins that depend on it.
+ Width = 640,
+ Height = 480,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
{
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- Texture = source.GetTexture("spinner-background"),
- Y = background_y_offset,
- Scale = new Vector2(sprite_scale)
- },
- disc = new Sprite
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Texture = source.GetTexture("spinner-circle"),
- Scale = new Vector2(sprite_scale)
- },
- metre = new Container
- {
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
- Y = background_y_offset,
- Masking = true,
- Child = new Sprite
+ new Sprite
{
- Texture = source.GetTexture("spinner-metre"),
- Anchor = Anchor.BottomCentre,
- Origin = Anchor.BottomCentre,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Texture = source.GetTexture("spinner-background"),
+ Scale = new Vector2(sprite_scale)
},
- Scale = new Vector2(0.625f)
+ disc = new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Texture = source.GetTexture("spinner-circle"),
+ Scale = new Vector2(sprite_scale)
+ },
+ metre = new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ // this anchor makes no sense, but that's what stable uses.
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ // adjustment for stable (metre has additional offset)
+ Margin = new MarginPadding { Top = 20 },
+ Masking = true,
+ Child = metreSprite = new Sprite
+ {
+ Texture = source.GetTexture("spinner-metre"),
+ Anchor = Anchor.TopLeft,
+ Origin = Anchor.TopLeft,
+ Scale = new Vector2(0.625f)
+ }
+ }
}
};
}
- private Vector2 metreFinalSize;
-
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeOut();
drawableSpinner.State.BindValueChanged(updateStateTransforms, true);
-
- metreFinalSize = metre.Size = metre.Child.Size;
}
private void updateStateTransforms(ValueChangedEvent state)
@@ -93,7 +101,16 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
base.Update();
disc.Rotation = drawableSpinner.RotationTracker.Rotation;
- metre.Height = getMetreHeight(drawableSpinner.Progress);
+
+ // careful: need to call this exactly once for all calculations in a frame
+ // as the function has a random factor in it
+ var metreHeight = getMetreHeight(drawableSpinner.Progress);
+
+ // hack to make the metre blink up from below than down from above.
+ // move down the container to be able to apply masking for the metre,
+ // and then move the sprite back up the same amount to keep its position absolute.
+ metre.Y = final_metre_height - metreHeight;
+ metreSprite.Y = -metre.Y;
}
private const int total_bars = 10;
@@ -108,7 +125,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
if (RNG.NextBool(((int)progress % 10) / 10f))
barCount++;
- return (float)barCount / total_bars * metreFinalSize.Y;
+ return (float)barCount / total_bars * final_metre_height;
}
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
index 0f586034d5..25ab96445a 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBall.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
[BackgroundDependencyLoader]
private void load(ISkinSource skin, DrawableHitObject drawableObject)
{
- animationContent.Colour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
+ var ballColour = skin.GetConfig(OsuSkinColour.SliderBall)?.Value ?? Color4.White;
InternalChildren = new[]
{
@@ -39,11 +39,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
Texture = skin.GetTexture("sliderb-nd"),
Colour = new Color4(5, 5, 5, 255),
},
- animationContent.With(d =>
+ LegacyColourCompatibility.ApplyWithDoubledAlpha(animationContent.With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
- }),
+ }), ballColour),
layerSpec = new Sprite
{
Anchor = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
index 21df49d80b..aad8b189d9 100644
--- a/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/LegacySliderBody.cs
@@ -18,6 +18,10 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
+ protected new float CalculatedBorderPortion
+ // Roughly matches osu!stable's slider border portions.
+ => base.CalculatedBorderPortion * 0.77f;
+
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
protected override Color4 ColourAt(float position)
diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
index d0c57b20c0..5e550a5d03 100644
--- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs
@@ -12,6 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests
{
[TestFixture]
+ [Timeout(10000)]
public class TaikoBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index e896606ee8..b59f3a4344 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -2,7 +2,7 @@
-
+
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
index b595f43fbb..16a0726c8c 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Taiko.Objects;
@@ -22,20 +21,20 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
new BeatmapStatistic
{
Name = @"Hit Count",
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
Content = hits.ToString(),
- Icon = FontAwesome.Regular.Circle
},
new BeatmapStatistic
{
Name = @"Drumroll Count",
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
Content = drumrolls.ToString(),
- Icon = FontAwesome.Regular.Circle
},
new BeatmapStatistic
{
Name = @"Swell Count",
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
Content = swells.ToString(),
- Icon = FontAwesome.Regular.Circle
}
};
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
index bfcf268c3d..9b73ccd248 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyCirclePiece.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
private void updateAccentColour()
{
- backgroundLayer.Colour = accentColour;
+ backgroundLayer.Colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
index 8223e3bc01..5ab8e3a8c8 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyDrumRoll.cs
@@ -76,9 +76,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning
private void updateAccentColour()
{
- headCircle.AccentColour = accentColour;
- body.Colour = accentColour;
- end.Colour = accentColour;
+ var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
+
+ headCircle.AccentColour = colour;
+ body.Colour = colour;
+ end.Colour = colour;
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs
index 656728f6e4..b11b64c22c 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/LegacyHit.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Skinning
@@ -18,9 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning
[BackgroundDependencyLoader]
private void load()
{
- AccentColour = component == TaikoSkinComponents.CentreHit
- ? new Color4(235, 69, 44, 255)
- : new Color4(67, 142, 172, 255);
+ AccentColour = LegacyColourCompatibility.DisallowZeroAlpha(
+ component == TaikoSkinComponents.CentreHit
+ ? new Color4(235, 69, 44, 255)
+ : new Color4(67, 142, 172, 255));
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
index 2011842591..9d485e3f20 100644
--- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
+++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs
@@ -22,7 +22,6 @@ using osu.Game.Rulesets.Taiko.Scoring;
using osu.Game.Scoring;
using System;
using System.Linq;
-using osu.Framework.Testing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Taiko.Edit;
using osu.Game.Rulesets.Taiko.Objects;
@@ -32,7 +31,6 @@ using osu.Game.Skinning;
namespace osu.Game.Rulesets.Taiko
{
- [ExcludeFromDynamicCompile]
public class TaikoRuleset : Ruleset, ILegacyRuleset
{
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods);
@@ -161,19 +159,34 @@ namespace osu.Game.Rulesets.Taiko
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame();
- public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
+ public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap)
{
- new StatisticRow
+ var timedHitEvents = score.HitEvents.Where(e => e.HitObject is Hit).ToList();
+
+ return new[]
{
- Columns = new[]
+ new StatisticRow
{
- new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList())
+ Columns = new[]
{
- RelativeSizeAxes = Axes.X,
- Height = 250
- }),
+ new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(timedHitEvents)
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250
+ }),
+ }
+ },
+ new StatisticRow
+ {
+ Columns = new[]
+ {
+ new StatisticItem(string.Empty, new SimpleStatisticTable(3, new SimpleStatisticItem[]
+ {
+ new UnstableRate(timedHitEvents)
+ }))
+ }
}
- }
- };
+ };
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs
new file mode 100644
index 0000000000..0f6d956b3c
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/BeatmapDifficultyManagerTest.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu.Mods;
+
+namespace osu.Game.Tests.Beatmaps
+{
+ [TestFixture]
+ public class BeatmapDifficultyManagerTest
+ {
+ [Test]
+ public void TestKeyEqualsWithDifferentModInstances()
+ {
+ var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+
+ Assert.That(key1, Is.EqualTo(key2));
+ }
+
+ [Test]
+ public void TestKeyEqualsWithDifferentModOrder()
+ {
+ var key1 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
+ var key2 = new BeatmapDifficultyManager.DifficultyCacheLookup(1234, 0, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
+
+ Assert.That(key1, Is.EqualTo(key2));
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index 30331e98d2..8b22309033 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -10,6 +10,7 @@ using System.Text;
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
@@ -19,6 +20,7 @@ using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
+using osu.Game.Skinning;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats
@@ -26,18 +28,33 @@ namespace osu.Game.Tests.Beatmaps.Formats
[TestFixture]
public class LegacyBeatmapEncoderTest
{
- private static IEnumerable allBeatmaps => TestResources.GetStore().GetAvailableResources().Where(res => res.EndsWith(".osu"));
+ private static readonly DllResourceStore beatmaps_resource_store = TestResources.GetStore();
+
+ private static IEnumerable allBeatmaps = beatmaps_resource_store.GetAvailableResources().Where(res => res.EndsWith(".osu"));
[TestCaseSource(nameof(allBeatmaps))]
public void TestEncodeDecodeStability(string name)
{
- var decoded = decodeFromLegacy(TestResources.GetStore().GetStream(name));
- var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded));
+ var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
+ var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
- sort(decoded);
- sort(decodedAfterEncode);
+ sort(decoded.beatmap);
+ sort(decodedAfterEncode.beatmap);
- Assert.That(decodedAfterEncode.Serialize(), Is.EqualTo(decoded.Serialize()));
+ Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
+ Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
+ }
+
+ private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
+ {
+ // equal to null, no need to SequenceEqual
+ if (a.ComboColours == null && b.ComboColours == null)
+ return true;
+
+ if (a.ComboColours == null || b.ComboColours == null)
+ return false;
+
+ return a.ComboColours.SequenceEqual(b.ComboColours);
}
private void sort(IBeatmap beatmap)
@@ -50,18 +67,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
- private IBeatmap decodeFromLegacy(Stream stream)
+ private (IBeatmap beatmap, TestLegacySkin beatmapSkin) decodeFromLegacy(Stream stream, string name)
{
using (var reader = new LineBufferedReader(stream))
- return convert(new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader));
+ {
+ var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader);
+ var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name);
+ return (convert(beatmap), beatmapSkin);
+ }
}
- private Stream encodeToLegacy(IBeatmap beatmap)
+ private class TestLegacySkin : LegacySkin
{
+ public TestLegacySkin(IResourceStore storage, string fileName)
+ : base(new SkinInfo { Name = "Test Skin", Creator = "Craftplacer" }, storage, null, fileName)
+ {
+ }
+ }
+
+ private Stream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
+ {
+ var (beatmap, beatmapSkin) = fullBeatmap;
var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
- new LegacyBeatmapEncoder(beatmap).Encode(writer);
+ new LegacyBeatmapEncoder(beatmap, beatmapSkin).Encode(writer);
stream.Position = 0;
@@ -106,7 +136,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
protected override Texture GetBackground() => throw new NotImplementedException();
- protected override Track GetTrack() => throw new NotImplementedException();
+ protected override Track GetBeatmapTrack() => throw new NotImplementedException();
}
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
new file mode 100644
index 0000000000..9c71466489
--- /dev/null
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
@@ -0,0 +1,70 @@
+// 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 NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Catch;
+using osu.Game.Rulesets.Mania;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Rulesets.Taiko;
+using osu.Game.Scoring;
+using osu.Game.Scoring.Legacy;
+using osu.Game.Tests.Resources;
+
+namespace osu.Game.Tests.Beatmaps.Formats
+{
+ [TestFixture]
+ public class LegacyScoreDecoderTest
+ {
+ [Test]
+ public void TestDecodeManiaReplay()
+ {
+ var decoder = new TestLegacyScoreDecoder();
+
+ using (var resourceStream = TestResources.OpenResource("Replays/mania-replay.osr"))
+ {
+ var score = decoder.Parse(resourceStream);
+
+ Assert.AreEqual(3, score.ScoreInfo.Ruleset.ID);
+
+ Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
+ Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);
+
+ Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
+ Assert.AreEqual(3, score.ScoreInfo.MaxCombo);
+ Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
+ Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);
+
+ Assert.That(score.Replay.Frames, Is.Not.Empty);
+ }
+ }
+
+ private class TestLegacyScoreDecoder : LegacyScoreDecoder
+ {
+ private static readonly Dictionary rulesets = new Ruleset[]
+ {
+ new OsuRuleset(),
+ new TaikoRuleset(),
+ new CatchRuleset(),
+ new ManiaRuleset()
+ }.ToDictionary(ruleset => ((ILegacyRuleset)ruleset).LegacyID);
+
+ protected override Ruleset GetRuleset(int rulesetId) => rulesets[rulesetId];
+
+ protected override WorkingBeatmap GetBeatmap(string md5Hash) => new TestWorkingBeatmap(new Beatmap
+ {
+ BeatmapInfo = new BeatmapInfo
+ {
+ MD5Hash = md5Hash,
+ Ruleset = new OsuRuleset().RulesetInfo,
+ BaseDifficulty = new BeatmapDifficulty()
+ }
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 0151678db3..dd3dba1274 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -15,8 +15,10 @@ using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.IO;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Resources;
+using osu.Game.Users;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
@@ -32,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWhenClosed()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -49,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDelete()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -70,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -96,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithReZip()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -154,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithChangedFile()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -205,7 +207,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportThenImportWithDifferentFilename()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -257,7 +259,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportCorruptThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -299,7 +301,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestRollbackOnFailure()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -376,7 +378,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImport()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -404,7 +406,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost(set.ToString()))
{
try
{
@@ -438,7 +440,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWithDuplicateBeatmapIDs()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -524,7 +526,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWhenFileOpen()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -546,7 +548,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithDuplicateHashes()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateHashes)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -588,7 +590,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportNestedStructure()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -633,7 +635,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWithIgnoredDirectoryInArchive()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -687,7 +689,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestUpdateBeatmapInfo()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapInfo)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -717,7 +719,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestUpdateBeatmapFile()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestUpdateBeatmapFile)))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -756,6 +758,63 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
+ [Test]
+ public void TestCreateNewEmptyBeatmap()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var manager = osu.Dependencies.Get();
+
+ var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
+
+ manager.Save(working.BeatmapInfo, working.Beatmap);
+
+ var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
+
+ // Check that the new file is referenced correctly by attempting a retrieval
+ Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
+ Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(0));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
+ [Test]
+ public void TestCreateNewBeatmapWithObject()
+ {
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
+ {
+ try
+ {
+ var osu = loadOsu(host);
+ var manager = osu.Dependencies.Get();
+
+ var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
+
+ ((Beatmap)working.Beatmap).HitObjects.Add(new HitCircle { StartTime = 5000 });
+
+ manager.Save(working.BeatmapInfo, working.Beatmap);
+
+ var retrievedSet = manager.GetAllUsableBeatmapSets()[0];
+
+ // Check that the new file is referenced correctly by attempting a retrieval
+ Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(retrievedSet.Beatmaps[0]).Beatmap;
+ Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1));
+ Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000));
+ }
+ finally
+ {
+ host.Exit();
+ }
+ }
+ }
+
public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
diff --git a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs
index ff17f23d50..b491157627 100644
--- a/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs
+++ b/osu.Game.Tests/Editing/LegacyEditorBeatmapPatcherTest.cs
@@ -351,7 +351,7 @@ namespace osu.Game.Tests.Editing
using (var encoded = new MemoryStream())
{
using (var sw = new StreamWriter(encoded))
- new LegacyBeatmapEncoder(beatmap).Encode(sw);
+ new LegacyBeatmapEncoder(beatmap, null).Encode(sw);
return encoded.ToArray();
}
diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
index cd3669f160..891537c4ad 100644
--- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs
@@ -1,10 +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;
using NUnit.Framework;
using osu.Framework.Testing;
-using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
@@ -19,7 +17,14 @@ namespace osu.Game.Tests.Gameplay
{
GameplayClockContainer gcc = null;
- AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0)));
+ AddStep("create container", () =>
+ {
+ var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ working.LoadTrack();
+
+ Add(gcc = new GameplayClockContainer(working, 0));
+ });
+
AddStep("start track", () => gcc.Start());
AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0);
}
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index b30870d057..a690eb3b59 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -59,7 +59,10 @@ namespace osu.Game.Tests.Gameplay
AddStep("create container", () =>
{
- Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0));
+ var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ working.LoadTrack();
+
+ Add(gameplayContainer = new GameplayClockContainer(working, 0));
gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1))
{
@@ -103,7 +106,7 @@ namespace osu.Game.Tests.Gameplay
Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio);
SelectedMods.Value = new[] { testedMod };
- Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0));
+ Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, 0));
gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1))
{
diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs
new file mode 100644
index 0000000000..ad6f01881b
--- /dev/null
+++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs
@@ -0,0 +1,43 @@
+// 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 NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Ranking.Statistics;
+
+namespace osu.Game.Tests.NonVisual.Ranking
+{
+ [TestFixture]
+ public class UnstableRateTest
+ {
+ [Test]
+ public void TestDistributedHits()
+ {
+ var events = Enumerable.Range(-5, 11)
+ .Select(t => new HitEvent(t - 5, HitResult.Great, new HitObject(), null, null));
+
+ var unstableRate = new UnstableRate(events);
+
+ Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10)));
+ }
+
+ [Test]
+ public void TestMissesAndEmptyWindows()
+ {
+ var events = new[]
+ {
+ new HitEvent(-100, HitResult.Miss, new HitObject(), null, null),
+ new HitEvent(0, HitResult.Great, new HitObject(), null, null),
+ new HitEvent(200, HitResult.Meh, new HitObject { HitWindows = HitWindows.Empty }, null, null),
+ };
+
+ var unstableRate = new UnstableRate(events);
+
+ Assert.AreEqual(0, unstableRate.Value);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Resources/Replays/mania-replay.osr b/osu.Game.Tests/Resources/Replays/mania-replay.osr
new file mode 100644
index 0000000000..da1a7bdd28
Binary files /dev/null and b/osu.Game.Tests/Resources/Replays/mania-replay.osr differ
diff --git a/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini
deleted file mode 100644
index 3c0dae6b13..0000000000
--- a/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[General]
-Version: latest
-
-[Colours]
-Combo1: 255,255,255,0
\ No newline at end of file
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index 57f0d7e957..a4d20714fa 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Scores.IO
[Test]
public async Task TestBasicImport()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -66,7 +66,7 @@ namespace osu.Game.Tests.Scores.IO
[Test]
public async Task TestImportMods()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -92,7 +92,7 @@ namespace osu.Game.Tests.Scores.IO
[Test]
public async Task TestImportStatistics()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -122,7 +122,7 @@ namespace osu.Game.Tests.Scores.IO
[Test]
public async Task TestImportWithDeletedBeatmapSet()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDeletedBeatmapSet"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
@@ -159,7 +159,7 @@ namespace osu.Game.Tests.Scores.IO
[Test]
public async Task TestOnlineScoreIsAvailableLocally()
{
- using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestOnlineScoreIsAvailableLocally"))
+ using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{
try
{
diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
index c408d2f182..aedf26ee75 100644
--- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
+++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs
@@ -108,15 +108,5 @@ namespace osu.Game.Tests.Skins
using (var stream = new LineBufferedReader(resStream))
Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m));
}
-
- [Test]
- public void TestDecodeColourWithZeroAlpha()
- {
- var decoder = new LegacySkinDecoder();
-
- using (var resStream = TestResources.OpenResource("skin-zero-alpha-colour.ini"))
- using (var stream = new LineBufferedReader(resStream))
- Assert.That(decoder.Decode(stream).ComboColours[0].A, Is.EqualTo(1.0f));
- }
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
index 4d3b73fb32..eff430ac25 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -26,6 +26,7 @@ namespace osu.Game.Tests.Skins
{
var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result;
beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]);
+ beatmap.LoadTrack();
}
[Test]
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs
new file mode 100644
index 0000000000..62e12158ab
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs
@@ -0,0 +1,32 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Osu.Beatmaps;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Edit.Setup;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ [TestFixture]
+ public class TestSceneSetupScreen : EditorClockTestScene
+ {
+ [Cached(typeof(EditorBeatmap))]
+ [Cached(typeof(IBeatSnapProvider))]
+ private readonly EditorBeatmap editorBeatmap;
+
+ public TestSceneSetupScreen()
+ {
+ editorBeatmap = new EditorBeatmap(new OsuBeatmap());
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
+ Child = new SetupScreen();
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
index 79275d70a7..6fd5511e5a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs
@@ -4,7 +4,6 @@
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Audio.Track;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Timing;
@@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public class TestSceneCompletionCancellation : OsuPlayerTestScene
{
- private Track track;
-
[Resolved]
private AudioManager audio { get; set; }
@@ -34,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
base.SetUpSteps();
// Ensure track has actually running before attempting to seek
- AddUntilStep("wait for track to start running", () => track.IsRunning);
+ AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
}
[Test]
@@ -73,13 +70,13 @@ namespace osu.Game.Tests.Visual.Gameplay
private void complete()
{
- AddStep("seek to completion", () => track.Seek(5000));
+ AddStep("seek to completion", () => Beatmap.Value.Track.Seek(5000));
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
}
private void cancel()
{
- AddStep("rewind to cancel", () => track.Seek(4000));
+ AddStep("rewind to cancel", () => Beatmap.Value.Track.Seek(4000));
AddUntilStep("completion cleared by processor", () => !Player.ScoreProcessor.HasCompleted.Value);
}
@@ -91,11 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- {
- var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
- track = working.Track;
- return working;
- }
+ => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index 2a119f5199..73c6970482 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -5,7 +5,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
-using osu.Framework.Audio.Track;
using osu.Framework.Utils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
@@ -21,19 +20,13 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved]
private AudioManager audioManager { get; set; }
- private Track track;
-
- protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- {
- var working = new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
- track = working.Track;
- return working;
- }
+ protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) =>
+ new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
[Test]
public void TestNoJudgementsOnRewind()
{
- AddUntilStep("wait for track to start running", () => track.IsRunning);
+ AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addSeekStep(3000);
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses >= 7));
@@ -46,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addSeekStep(double time)
{
- AddStep($"seek to {time}", () => track.Seek(time));
+ AddStep($"seek to {time}", () => Beatmap.Value.Track.Seek(time));
// Allow a few frames of lenience
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
new file mode 100644
index 0000000000..3ee0f4e720
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneOverlayActivation.cs
@@ -0,0 +1,54 @@
+// 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.Game.Overlays;
+using osu.Game.Rulesets;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneOverlayActivation : OsuPlayerTestScene
+ {
+ protected new OverlayTestPlayer Player => base.Player as OverlayTestPlayer;
+
+ [Test]
+ public void TestGameplayOverlayActivation()
+ {
+ AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
+ }
+
+ [Test]
+ public void TestGameplayOverlayActivationPaused()
+ {
+ AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
+ AddStep("pause gameplay", () => Player.Pause());
+ AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
+ }
+
+ [Test]
+ public void TestGameplayOverlayActivationReplayLoaded()
+ {
+ AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
+ AddStep("load a replay", () => Player.DrawableRuleset.HasReplayLoaded.Value = true);
+ AddAssert("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
+ }
+
+ [Test]
+ public void TestGameplayOverlayActivationBreaks()
+ {
+ AddAssert("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
+ AddStep("seek to break", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().StartTime));
+ AddUntilStep("activation mode is user triggered", () => Player.OverlayActivationMode == OverlayActivation.UserTriggered);
+ AddStep("seek to break end", () => Player.GameplayClockContainer.Seek(Beatmap.Value.Beatmap.Breaks.First().EndTime));
+ AddUntilStep("activation mode is disabled", () => Player.OverlayActivationMode == OverlayActivation.Disabled);
+ }
+
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new OverlayTestPlayer();
+
+ protected class OverlayTestPlayer : TestPlayer
+ {
+ public new OverlayActivation OverlayActivationMode => base.OverlayActivationMode.Value;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
index 7ed7a116b4..841722a8f1 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs
@@ -1,11 +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 System;
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;
using osuTK;
@@ -32,7 +30,10 @@ namespace osu.Game.Tests.Visual.Gameplay
requestCount = 0;
increment = skip_time;
- Child = gameplayClockContainer = new GameplayClockContainer(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), Array.Empty(), 0)
+ var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
+ working.LoadTrack();
+
+ Child = gameplayClockContainer = new GameplayClockContainer(working, 0)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
index 9f1492a25f..5a2b8d22fd 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs
@@ -22,19 +22,32 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture]
public class TestSceneStoryboard : OsuTestScene
{
- private readonly Container storyboardContainer;
+ private Container storyboardContainer;
private DrawableStoryboard storyboard;
- [Cached]
- private MusicController musicController = new MusicController();
+ [Test]
+ public void TestStoryboard()
+ {
+ AddStep("Restart", restart);
+ AddToggleStep("Passing", passing =>
+ {
+ if (storyboard != null) storyboard.Passing = passing;
+ });
+ }
- public TestSceneStoryboard()
+ [Test]
+ public void TestStoryboardMissingVideo()
+ {
+ AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
{
Clock = new FramedClock();
AddRange(new Drawable[]
{
- musicController,
new Container
{
RelativeSizeAxes = Axes.Both,
@@ -58,32 +71,11 @@ namespace osu.Game.Tests.Visual.Gameplay
State = { Value = Visibility.Visible },
}
});
+
+ Beatmap.BindValueChanged(beatmapChanged, true);
}
- [Test]
- public void TestStoryboard()
- {
- AddStep("Restart", restart);
- AddToggleStep("Passing", passing =>
- {
- if (storyboard != null) storyboard.Passing = passing;
- });
- }
-
- [Test]
- public void TestStoryboardMissingVideo()
- {
- AddStep("Load storyboard with missing video", loadStoryboardNoVideo);
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- Beatmap.ValueChanged += beatmapChanged;
- }
-
- private void beatmapChanged(ValueChangedEvent e)
- => loadStoryboard(e.NewValue);
+ private void beatmapChanged(ValueChangedEvent e) => loadStoryboard(e.NewValue);
private void restart()
{
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs
index 8f20e38494..5f135febf4 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs
@@ -2,8 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Audio.Track;
using osu.Framework.Screens;
+using osu.Framework.Utils;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
@@ -15,11 +15,9 @@ namespace osu.Game.Tests.Visual.Menus
public TestSceneIntroWelcome()
{
- AddUntilStep("wait for load", () => getTrack() != null);
-
- AddAssert("check if menu music loops", () => getTrack().Looping);
+ AddUntilStep("wait for load", () => MusicController.TrackLoaded);
+ AddAssert("correct track", () => Precision.AlmostEquals(MusicController.CurrentTrack.Length, 48000, 1));
+ AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping);
}
-
- private Track getTrack() => (IntroStack?.CurrentScreen as MainMenu)?.Track;
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
new file mode 100644
index 0000000000..4cad2b19d5
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
@@ -0,0 +1,83 @@
+// 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 NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Input.Bindings;
+using osu.Game.Overlays;
+using osu.Game.Tests.Resources;
+using osu.Game.Tests.Visual.Navigation;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ public class TestSceneMusicActionHandling : OsuGameTestScene
+ {
+ private GlobalActionContainer globalActionContainer => Game.ChildrenOfType().First();
+
+ [Test]
+ public void TestMusicPlayAction()
+ {
+ AddStep("ensure playing something", () => Game.MusicController.EnsurePlayingSomething());
+ AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
+ AddAssert("music paused", () => !Game.MusicController.IsPlaying && Game.MusicController.IsUserPaused);
+ AddStep("toggle playback", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPlay));
+ AddAssert("music resumed", () => Game.MusicController.IsPlaying && !Game.MusicController.IsUserPaused);
+ }
+
+ [Test]
+ public void TestMusicNavigationActions()
+ {
+ int importId = 0;
+ Queue<(WorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
+
+ // ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
+ AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(new BeatmapSetInfo
+ {
+ Beatmaps = new List
+ {
+ new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty(),
+ }
+ },
+ Metadata = new BeatmapMetadata
+ {
+ Artist = $"a test map {importId++}",
+ Title = "title",
+ }
+ }).Wait(), 5);
+
+ AddStep("import beatmap with track", () =>
+ {
+ var setWithTrack = Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result;
+ Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First());
+ });
+
+ AddStep("bind to track change", () =>
+ {
+ trackChangeQueue = new Queue<(WorkingBeatmap, TrackChangeDirection)>();
+ Game.MusicController.TrackChanged += (working, changeDirection) => trackChangeQueue.Enqueue((working, changeDirection));
+ });
+
+ AddStep("seek track to 6 second", () => Game.MusicController.SeekTo(6000));
+ AddUntilStep("wait for current time to update", () => Game.MusicController.CurrentTrack.CurrentTime > 5000);
+
+ AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
+ AddAssert("no track change", () => trackChangeQueue.Count == 0);
+ AddUntilStep("track restarted", () => Game.MusicController.CurrentTrack.CurrentTime < 5000);
+
+ AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
+ AddAssert("track changed to previous", () =>
+ trackChangeQueue.Count == 1 &&
+ trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Prev);
+
+ AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
+ AddAssert("track changed to next", () =>
+ trackChangeQueue.Count == 1 &&
+ trackChangeQueue.Dequeue().changeDirection == TrackChangeDirection.Next);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs
index d7f23f5cc0..4b22af38c5 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneSongTicker.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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
@@ -11,14 +10,10 @@ namespace osu.Game.Tests.Visual.Menus
{
public class TestSceneSongTicker : OsuTestScene
{
- [Cached]
- private MusicController musicController = new MusicController();
-
public TestSceneSongTicker()
{
AddRange(new Drawable[]
{
- musicController,
new SongTicker
{
Anchor = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
index f819ae4682..2a4486812c 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Menus
public class TestToolbar : Toolbar
{
- public new Bindable OverlayActivationMode => base.OverlayActivationMode;
+ public new Bindable OverlayActivationMode => base.OverlayActivationMode as Bindable;
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs
index 3d225aa0a9..faea32f90f 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs
@@ -162,6 +162,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value));
}
+ ///
+ /// Tests that the global mod instances are not retained by the rooms, as global mod instances are retained and re-used by the mod select overlay.
+ ///
+ [Test]
+ public void TestGlobalModInstancesNotRetained()
+ {
+ OsuModDoubleTime mod = null;
+
+ AddStep("set dt mod and store", () =>
+ {
+ SelectedMods.Value = new[] { new OsuModDoubleTime() };
+
+ // Mod select overlay replaces our mod.
+ mod = (OsuModDoubleTime)SelectedMods.Value[0];
+ });
+
+ AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
+
+ AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
+ AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
+ }
+
private class TestMatchSongSelect : MatchSongSelect
{
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
index b2e18849c9..a899d072ac 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
+using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index 8ccaca8630..73a833c15d 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -4,7 +4,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
@@ -46,7 +45,6 @@ namespace osu.Game.Tests.Visual.Navigation
Player player = null;
WorkingBeatmap beatmap() => Game.Beatmap.Value;
- Track track() => beatmap().Track;
PushAndConfirm(() => new TestSongSelect());
@@ -62,30 +60,27 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
AddUntilStep("wait for fail", () => player.HasFailed);
- AddUntilStep("wait for track stop", () => !track().IsRunning);
- AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
+ AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
+ AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
pushEscape();
- AddUntilStep("wait for track playing", () => track().IsRunning);
- AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
+ AddUntilStep("wait for track playing", () => Game.MusicController.IsPlaying);
+ AddAssert("Ensure time wasn't reset to preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().Metadata.PreviewTime);
}
[Test]
public void TestMenuMakesMusic()
{
- WorkingBeatmap beatmap() => Game.Beatmap.Value;
- Track track() => beatmap().Track;
-
TestSongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestSongSelect());
- AddUntilStep("wait for no track", () => track() is TrackVirtual);
+ AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
AddStep("return to menu", () => songSelect.Exit());
- AddUntilStep("wait for track", () => !(track() is TrackVirtual) && track().IsRunning);
+ AddUntilStep("wait for track", () => !Game.MusicController.CurrentTrack.IsDummyDevice && Game.MusicController.IsPlaying);
}
[Test]
@@ -140,12 +135,12 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("Wait for music controller", () => Game.MusicController.IsLoaded);
AddStep("Seek close to end", () =>
{
- Game.MusicController.SeekTo(Game.Beatmap.Value.Track.Length - 1000);
- Game.Beatmap.Value.Track.Completed += () => trackCompleted = true;
+ Game.MusicController.SeekTo(Game.MusicController.CurrentTrack.Length - 1000);
+ Game.MusicController.CurrentTrack.Completed += () => trackCompleted = true;
});
AddUntilStep("Track was completed", () => trackCompleted);
- AddUntilStep("Track was restarted", () => Game.Beatmap.Value.Track.IsRunning);
+ AddUntilStep("Track was restarted", () => Game.MusicController.IsPlaying);
}
private void pushEscape() =>
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs
index e60adcee34..8f20bcdcc1 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online
[TestFixture]
public class TestSceneFullscreenOverlay : OsuTestScene
{
- private FullscreenOverlay overlay;
+ private FullscreenOverlay overlay;
protected override void LoadComplete()
{
@@ -38,10 +38,10 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("fire count 3", () => fireCount == 3);
}
- private class TestFullscreenOverlay : FullscreenOverlay
+ private class TestFullscreenOverlay : FullscreenOverlay
{
public TestFullscreenOverlay()
- : base(OverlayColourScheme.Pink)
+ : base(OverlayColourScheme.Pink, null)
{
Children = new Drawable[]
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
index 103308d34d..9662bd65b4 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs
@@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby());
- AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(null, null)
+ AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
{
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null }
});
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs
index 7ca1fc842f..144f8da2fa 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs
@@ -35,6 +35,18 @@ namespace osu.Game.Tests.Visual.Ranking
createTest(new List());
}
+ [Test]
+ public void TestMissesDontShow()
+ {
+ createTest(Enumerable.Range(0, 100).Select(i =>
+ {
+ if (i % 2 == 0)
+ return new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null);
+
+ return new HitEvent(30, HitResult.Miss, new HitCircle(), new HitCircle(), null);
+ }).ToList());
+ }
+
private void createTest(List events) => AddStep("create test", () =>
{
Children = new Drawable[]
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
index 74808bc2f5..03cb5fa3db 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
@@ -13,6 +13,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
using osu.Game.Scoring;
@@ -212,6 +213,25 @@ namespace osu.Game.Tests.Visual.Ranking
AddAssert("expanded panel still on screen", () => this.ChildrenOfType().Single(p => p.State == PanelState.Expanded).ScreenSpaceDrawQuad.TopLeft.X > 0);
}
+ [Test]
+ public void TestDownloadButtonInitiallyDisabled()
+ {
+ TestResultsScreen screen = null;
+
+ AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen()));
+
+ AddAssert("download button is disabled", () => !screen.ChildrenOfType().Single().Enabled.Value);
+
+ AddStep("click contracted panel", () =>
+ {
+ var contractedPanel = this.ChildrenOfType().First(p => p.State == PanelState.Contracted && p.ScreenSpaceDrawQuad.TopLeft.X > screen.ScreenSpaceDrawQuad.TopLeft.X);
+ InputManager.MoveMouseTo(contractedPanel);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("download button is enabled", () => screen.ChildrenOfType().Single().Enabled.Value);
+ }
+
private class TestResultsContainer : Container
{
[Cached(typeof(Player))]
@@ -255,6 +275,7 @@ namespace osu.Game.Tests.Visual.Ranking
{
var score = new TestScoreInfo(new OsuRuleset().RulesetInfo);
score.TotalScore += 10 - i;
+ score.Hash = $"test{i}";
scores.Add(score);
}
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs
similarity index 88%
rename from osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs
rename to osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs
index aa569e47b1..07a0bcc8d8 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticRow.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneSimpleStatisticTable.cs
@@ -13,7 +13,7 @@ using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Tests.Visual.Ranking
{
- public class TestSceneSimpleStatisticRow : OsuTestScene
+ public class TestSceneSimpleStatisticTable : OsuTestScene
{
private Container container;
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Ranking
public void TestEmpty()
{
AddStep("create with no items",
- () => container.Add(new SimpleStatisticRow(2, Enumerable.Empty())));
+ () => container.Add(new SimpleStatisticTable(2, Enumerable.Empty())));
}
[Test]
@@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Ranking
Value = RNG.Next(100)
});
- container.Add(new SimpleStatisticRow(columnCount, items));
+ container.Add(new SimpleStatisticTable(columnCount, items));
});
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 48b718c04d..67cd720260 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -5,9 +5,9 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Select.Leaderboards;
@@ -53,53 +53,46 @@ namespace osu.Game.Tests.Visual.SongSelect
private void showPersonalBestWithNullPosition()
{
- leaderboard.TopScore = new APILegacyUserTopScoreInfo
+ leaderboard.TopScore = new ScoreInfo
{
- Position = null,
- Score = new APILegacyScoreInfo
+ Rank = ScoreRank.XH,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() },
+ User = new User
{
- Rank = ScoreRank.XH,
- Accuracy = 1,
- MaxCombo = 244,
- TotalScore = 1707827,
- Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
- User = new User
+ Id = 6602580,
+ Username = @"waaiiru",
+ Country = new Country
{
- Id = 6602580,
- Username = @"waaiiru",
- Country = new Country
- {
- FullName = @"Spain",
- FlagName = @"ES",
- },
+ FullName = @"Spain",
+ FlagName = @"ES",
},
- }
+ },
};
}
private void showPersonalBest()
{
- leaderboard.TopScore = new APILegacyUserTopScoreInfo
+ leaderboard.TopScore = new ScoreInfo
{
Position = 999,
- Score = new APILegacyScoreInfo
+ Rank = ScoreRank.XH,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ User = new User
{
- Rank = ScoreRank.XH,
- Accuracy = 1,
- MaxCombo = 244,
- TotalScore = 1707827,
- Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
- User = new User
+ Id = 6602580,
+ Username = @"waaiiru",
+ Country = new Country
{
- Id = 6602580,
- Username = @"waaiiru",
- Country = new Country
- {
- FullName = @"Spain",
- FlagName = @"ES",
- },
+ FullName = @"Spain",
+ FlagName = @"ES",
},
- }
+ },
};
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
index 0598324110..b8b8792b9b 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs
@@ -6,11 +6,11 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osuTK.Graphics;
-using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Leaderboards;
using osu.Game.Overlays;
+using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Rulesets.Osu.Mods;
-using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.SongSelect
@@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.SongSelect
public TestSceneUserTopScoreContainer()
{
- UserTopScoreContainer topScoreContainer;
+ UserTopScoreContainer topScoreContainer;
Add(dialogOverlay = new DialogOverlay
{
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.SongSelect
RelativeSizeAxes = Axes.Both,
Colour = Color4.DarkGreen,
},
- topScoreContainer = new UserTopScoreContainer
+ topScoreContainer = new UserTopScoreContainer(s => new LeaderboardScore(s, s.Position, false))
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
@@ -52,69 +52,60 @@ namespace osu.Game.Tests.Visual.SongSelect
var scores = new[]
{
- new APILegacyUserTopScoreInfo
+ new ScoreInfo
{
Position = 999,
- Score = new APILegacyScoreInfo
+ Rank = ScoreRank.XH,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
+ User = new User
{
- Rank = ScoreRank.XH,
- Accuracy = 1,
- MaxCombo = 244,
- TotalScore = 1707827,
- Mods = new[] { new OsuModHidden().Acronym, new OsuModHardRock().Acronym, },
- User = new User
+ Id = 6602580,
+ Username = @"waaiiru",
+ Country = new Country
{
- Id = 6602580,
- Username = @"waaiiru",
- Country = new Country
- {
- FullName = @"Spain",
- FlagName = @"ES",
- },
+ FullName = @"Spain",
+ FlagName = @"ES",
},
- }
+ },
},
- new APILegacyUserTopScoreInfo
+ new ScoreInfo
{
Position = 110000,
- Score = new APILegacyScoreInfo
+ Rank = ScoreRank.X,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ User = new User
{
- Rank = ScoreRank.X,
- Accuracy = 1,
- MaxCombo = 244,
- TotalScore = 1707827,
- User = new User
+ Id = 4608074,
+ Username = @"Skycries",
+ Country = new Country
{
- Id = 4608074,
- Username = @"Skycries",
- Country = new Country
- {
- FullName = @"Brazil",
- FlagName = @"BR",
- },
+ FullName = @"Brazil",
+ FlagName = @"BR",
},
- }
+ },
},
- new APILegacyUserTopScoreInfo
+ new ScoreInfo
{
Position = 22333,
- Score = new APILegacyScoreInfo
+ Rank = ScoreRank.S,
+ Accuracy = 1,
+ MaxCombo = 244,
+ TotalScore = 1707827,
+ User = new User
{
- Rank = ScoreRank.S,
- Accuracy = 1,
- MaxCombo = 244,
- TotalScore = 1707827,
- User = new User
+ Id = 1541390,
+ Username = @"Toukai",
+ Country = new Country
{
- Id = 1541390,
- Username = @"Toukai",
- Country = new Country
- {
- FullName = @"Canada",
- FlagName = @"CA",
- },
+ FullName = @"Canada",
+ FlagName = @"CA",
},
- }
+ },
}
};
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
index dd5ceec739..82b7e65c4f 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs
@@ -26,9 +26,6 @@ namespace osu.Game.Tests.Visual.UserInterface
{
private readonly NowPlayingOverlay np;
- [Cached]
- private MusicController musicController = new MusicController();
-
public TestSceneBeatSyncedContainer()
{
Clock = new FramedClock();
@@ -36,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface
AddRange(new Drawable[]
{
- musicController,
new BeatContainer
{
Anchor = Anchor.BottomCentre,
@@ -71,6 +67,9 @@ namespace osu.Game.Tests.Visual.UserInterface
private readonly Box flashLayer;
+ [Resolved]
+ private MusicController musicController { get; set; }
+
public BeatContainer()
{
RelativeSizeAxes = Axes.X;
@@ -165,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface
if (timingPoints.Count == 0) return 0;
if (timingPoints[^1] == current)
- return (int)Math.Ceiling((Beatmap.Value.Track.Length - current.Time) / current.BeatLength);
+ return (int)Math.Ceiling((musicController.CurrentTrack.Length - current.Time) / current.BeatLength);
return (int)Math.Ceiling((getNextTimingPoint(current).Time - current.Time) / current.BeatLength);
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
index eb4750a597..e54292f7cc 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Platform;
using osu.Framework.Testing;
@@ -79,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.Cache(rulesetStore = new RulesetStore(ContextFactory));
- dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, Audio, dependencies.Get(), Beatmap.Default));
+ dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), dependencies.Get(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory));
beatmap = beatmapManager.Import(TestResources.GetTestBeatmapForImport()).Result.Beatmaps[0];
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
index 532744a0fc..475ab0c414 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNowPlayingOverlay.cs
@@ -1,15 +1,10 @@
// 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.Allocation;
-using osu.Framework.Audio;
using osu.Framework.Graphics;
-using osu.Framework.Platform;
-using osu.Game.Beatmaps;
using osu.Game.Overlays;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Tests.Visual.UserInterface
@@ -20,18 +15,11 @@ namespace osu.Game.Tests.Visual.UserInterface
[Cached]
private MusicController musicController = new MusicController();
- private WorkingBeatmap currentBeatmap;
-
private NowPlayingOverlay nowPlayingOverlay;
- private RulesetStore rulesets;
-
[BackgroundDependencyLoader]
- private void load(AudioManager audio, GameHost host)
+ private void load()
{
- Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
- Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default));
-
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
nowPlayingOverlay = new NowPlayingOverlay
@@ -51,44 +39,5 @@ namespace osu.Game.Tests.Visual.UserInterface
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
AddStep(@"hide", () => nowPlayingOverlay.Hide());
}
-
- private BeatmapManager manager { get; set; }
-
- private int importId;
-
- [Test]
- public void TestPrevTrackBehavior()
- {
- // ensure we have at least two beatmaps available.
- AddRepeatStep("import beatmap", () => manager.Import(new BeatmapSetInfo
- {
- Beatmaps = new List
- {
- new BeatmapInfo
- {
- BaseDifficulty = new BeatmapDifficulty(),
- }
- },
- Metadata = new BeatmapMetadata
- {
- Artist = $"a test map {importId++}",
- Title = "title",
- }
- }).Wait(), 5);
-
- AddStep(@"Next track", () => musicController.NextTrack());
- AddStep("Store track", () => currentBeatmap = Beatmap.Value);
-
- AddStep(@"Seek track to 6 second", () => musicController.SeekTo(6000));
- AddUntilStep(@"Wait for current time to update", () => currentBeatmap.Track.CurrentTime > 5000);
-
- AddStep(@"Set previous", () => musicController.PreviousTrack());
-
- AddAssert(@"Check beatmap didn't change", () => currentBeatmap == Beatmap.Value);
- AddUntilStep("Wait for current time to update", () => currentBeatmap.Track.CurrentTime < 5000);
-
- AddStep(@"Set previous", () => musicController.PreviousTrack());
- AddAssert(@"Check beatmap did change", () => currentBeatmap != Beatmap.Value);
- }
}
}
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index 90c91eb007..7dc5ce1d7f 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Tests
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
- protected override Track GetTrack() => trackStore.Get(firstAudioFile);
+ protected override Track GetBeatmapTrack() => trackStore.Get(firstAudioFile);
private string firstAudioFile
{
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index d767973528..c692bcd5e4 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -3,7 +3,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs
new file mode 100644
index 0000000000..33165d385a
--- /dev/null
+++ b/osu.Game.Tournament.Tests/Components/TestSceneDateTextBox.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Game.Tests.Visual;
+using osu.Game.Tournament.Components;
+using osuTK;
+using osuTK.Input;
+
+namespace osu.Game.Tournament.Tests.Components
+{
+ public class TestSceneDateTextBox : OsuManualInputManagerTestScene
+ {
+ private DateTextBox textBox;
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ Child = textBox = new DateTextBox
+ {
+ Width = 0.3f
+ };
+ });
+
+ [Test]
+ public void TestCommitWithoutSettingBindable()
+ {
+ AddStep("click textbox", () =>
+ {
+ InputManager.MoveMouseTo(textBox);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddStep("unfocus", () =>
+ {
+ InputManager.MoveMouseTo(Vector2.Zero);
+ InputManager.Click(MouseButton.Left);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 95f5deb2cc..5d55196dcf 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs
index ee7e350970..aee5241e35 100644
--- a/osu.Game.Tournament/Components/DateTextBox.cs
+++ b/osu.Game.Tournament/Components/DateTextBox.cs
@@ -22,11 +22,12 @@ namespace osu.Game.Tournament.Components
}
// hold a reference to the provided bindable so we don't have to in every settings section.
- private Bindable bindable;
+ private Bindable bindable = new Bindable();
public DateTextBox()
{
base.Bindable = new Bindable();
+
((OsuTextBox)Control).OnCommit = (sender, newText) =>
{
try
diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
index f3eecf8afe..efec4cffdd 100644
--- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs
@@ -26,6 +26,8 @@ namespace osu.Game.Tournament.Screens.Editors
[Cached]
private LadderEditorInfo editorInfo = new LadderEditorInfo();
+ private WarningBox rightClickMessage;
+
protected override bool DrawLoserPaths => true;
[BackgroundDependencyLoader]
@@ -37,6 +39,16 @@ namespace osu.Game.Tournament.Screens.Editors
Origin = Anchor.TopRight,
Margin = new MarginPadding(5)
});
+
+ AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));
+
+ LadderInfo.Matches.CollectionChanged += (_, __) => updateMessage();
+ updateMessage();
+ }
+
+ private void updateMessage()
+ {
+ rightClickMessage.Alpha = LadderInfo.Matches.Count > 0 ? 0 : 1;
}
public void BeginJoin(TournamentMatch match, bool losers)
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index 307ee1c773..bbe4a53d8f 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -87,30 +87,7 @@ namespace osu.Game.Tournament
},
}
},
- heightWarning = new Container
- {
- Masking = true,
- CornerRadius = 5,
- Depth = float.MinValue,
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- AutoSizeAxes = Axes.Both,
- Children = new Drawable[]
- {
- new Box
- {
- Colour = Color4.Red,
- RelativeSizeAxes = Axes.Both,
- },
- new TournamentSpriteText
- {
- Text = "Please make the window wider",
- Font = OsuFont.Torus.With(weight: FontWeight.Bold),
- Colour = Color4.White,
- Padding = new MarginPadding(20)
- }
- }
- },
+ heightWarning = new WarningBox("Please make the window wider"),
new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
diff --git a/osu.Game.Tournament/WarningBox.cs b/osu.Game.Tournament/WarningBox.cs
new file mode 100644
index 0000000000..814482aea4
--- /dev/null
+++ b/osu.Game.Tournament/WarningBox.cs
@@ -0,0 +1,40 @@
+// 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.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Graphics;
+using osuTK.Graphics;
+
+namespace osu.Game.Tournament
+{
+ internal class WarningBox : Container
+ {
+ public WarningBox(string text)
+ {
+ Masking = true;
+ CornerRadius = 5;
+ Depth = float.MinValue;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ AutoSizeAxes = Axes.Both;
+
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Red,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new TournamentSpriteText
+ {
+ Text = text,
+ Font = OsuFont.Torus.With(weight: FontWeight.Bold),
+ Colour = Color4.White,
+ Padding = new MarginPadding(20)
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
index c02b6002d9..e9d26683c3 100644
--- a/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficultyManager.cs
@@ -89,8 +89,14 @@ namespace osu.Game.Beatmaps
if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out var existing, out var key))
return existing;
- return await Task.Factory.StartNew(() => computeDifficulty(key, beatmapInfo, rulesetInfo), cancellationToken,
- TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
+ return await Task.Factory.StartNew(() =>
+ {
+ // Computation may have finished in a previous task.
+ if (tryGetExisting(beatmapInfo, rulesetInfo, mods, out existing, out _))
+ return existing;
+
+ return computeDifficulty(key, beatmapInfo, rulesetInfo);
+ }, cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
}
///
@@ -245,7 +251,7 @@ namespace osu.Game.Beatmaps
updateScheduler?.Dispose();
}
- private readonly struct DifficultyCacheLookup : IEquatable
+ public readonly struct DifficultyCacheLookup : IEquatable
{
public readonly int BeatmapId;
public readonly int RulesetId;
@@ -261,7 +267,7 @@ namespace osu.Game.Beatmaps
public bool Equals(DifficultyCacheLookup other)
=> BeatmapId == other.BeatmapId
&& RulesetId == other.RulesetId
- && Mods.SequenceEqual(other.Mods);
+ && Mods.Select(m => m.Acronym).SequenceEqual(other.Mods.Select(m => m.Acronym));
public override int GetHashCode()
{
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 3860f12baa..c5be5810e9 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -7,6 +7,7 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
+using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
@@ -14,6 +15,7 @@ using osu.Game.Scoring;
namespace osu.Game.Beatmaps
{
+ [ExcludeFromDynamicCompile]
[Serializable]
public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey
{
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index b4b341634c..34bb578b2a 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -9,6 +9,7 @@ using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
@@ -26,6 +27,8 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
+using osu.Game.Users;
+using osu.Game.Skinning;
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
namespace osu.Game.Beatmaps
@@ -63,16 +66,16 @@ namespace osu.Game.Beatmaps
private readonly RulesetStore rulesets;
private readonly BeatmapStore beatmaps;
private readonly AudioManager audioManager;
- private readonly GameHost host;
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
+ private readonly TextureStore textureStore;
+ private readonly ITrackStore trackStore;
- public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
+ public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, GameHost host = null,
WorkingBeatmap defaultBeatmap = null)
: base(storage, contextFactory, api, new BeatmapStore(contextFactory), host)
{
this.rulesets = rulesets;
this.audioManager = audioManager;
- this.host = host;
DefaultBeatmap = defaultBeatmap;
@@ -83,6 +86,9 @@ namespace osu.Game.Beatmaps
beatmaps.ItemUpdated += removeWorkingCache;
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
+
+ textureStore = new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store));
+ trackStore = audioManager.GetTrackStore(Files.Store);
}
protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
@@ -90,6 +96,34 @@ namespace osu.Game.Beatmaps
protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path)?.ToLowerInvariant() == ".osz";
+ public WorkingBeatmap CreateNew(RulesetInfo ruleset, User user)
+ {
+ var metadata = new BeatmapMetadata
+ {
+ Artist = "artist",
+ Title = "title",
+ Author = user,
+ };
+
+ var set = new BeatmapSetInfo
+ {
+ Metadata = metadata,
+ Beatmaps = new List
+ {
+ new BeatmapInfo
+ {
+ BaseDifficulty = new BeatmapDifficulty(),
+ Ruleset = ruleset,
+ Metadata = metadata,
+ Version = "difficulty"
+ }
+ }
+ };
+
+ var working = Import(set).Result;
+ return GetWorkingBeatmap(working.Beatmaps.First());
+ }
+
protected override async Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default)
{
if (archive != null)
@@ -194,31 +228,42 @@ namespace osu.Game.Beatmaps
///
/// The to save the content against. The file referenced by will be replaced.
/// The content to write.
- public void Save(BeatmapInfo info, IBeatmap beatmapContent)
+ /// The beatmap content to write, null if to be omitted.
+ public void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
{
var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID));
using (var stream = new MemoryStream())
{
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
- new LegacyBeatmapEncoder(beatmapContent).Encode(sw);
+ new LegacyBeatmapEncoder(beatmapContent, beatmapSkin).Encode(sw);
stream.Seek(0, SeekOrigin.Begin);
using (ContextFactory.GetForWrite())
{
var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID);
+ var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
+
+ // grab the original file (or create a new one if not found).
+ var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
+
+ // metadata may have changed; update the path with the standard format.
+ beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu";
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
+ // update existing or populate new file's filename.
+ fileInfo.Filename = beatmapInfo.Path;
+
stream.Seek(0, SeekOrigin.Begin);
- UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream);
+ UpdateFile(setInfo, fileInfo, stream);
}
}
removeWorkingCache(info);
}
- private readonly WeakList workingCache = new WeakList();
+ private readonly WeakList workingCache = new WeakList();
///
/// Retrieve a instance for the provided
@@ -246,16 +291,13 @@ namespace osu.Game.Beatmaps
lock (workingCache)
{
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
+ if (working != null)
+ return working;
- if (working == null)
- {
- beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
+ beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata;
- workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store,
- new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager));
- }
+ workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, textureStore, trackStore, beatmapInfo, audioManager));
- previous?.TransferTo(working);
return working;
}
}
@@ -459,7 +501,7 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
- protected override Track GetTrack() => null;
+ protected override Track GetBeatmapTrack() => null;
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index 39c5ccab27..362c99ea3f 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -17,19 +17,25 @@ namespace osu.Game.Beatmaps
{
public partial class BeatmapManager
{
- protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
+ private class BeatmapManagerWorkingBeatmap : WorkingBeatmap
{
private readonly IResourceStore store;
+ private readonly TextureStore textureStore;
+ private readonly ITrackStore trackStore;
- public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, BeatmapInfo beatmapInfo, AudioManager audioManager)
+ public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, ITrackStore trackStore, BeatmapInfo beatmapInfo, AudioManager audioManager)
: base(beatmapInfo, audioManager)
{
this.store = store;
this.textureStore = textureStore;
+ this.trackStore = trackStore;
}
protected override IBeatmap GetBeatmap()
{
+ if (BeatmapInfo.Path == null)
+ return new Beatmap { BeatmapInfo = BeatmapInfo };
+
try
{
using (var stream = new LineBufferedReader(store.GetStream(getPathForFile(BeatmapInfo.Path))))
@@ -44,10 +50,6 @@ namespace osu.Game.Beatmaps
private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
- private TextureStore textureStore;
-
- private ITrackStore trackStore;
-
protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes.
protected override Texture GetBackground()
@@ -66,11 +68,14 @@ namespace osu.Game.Beatmaps
}
}
- protected override Track GetTrack()
+ protected override Track GetBeatmapTrack()
{
+ if (Metadata?.AudioFile == null)
+ return null;
+
try
{
- return (trackStore ??= AudioManager.GetTrackStore(store)).Get(getPathForFile(Metadata.AudioFile));
+ return trackStore.Get(getPathForFile(Metadata.AudioFile));
}
catch (Exception e)
{
@@ -79,24 +84,11 @@ namespace osu.Game.Beatmaps
}
}
- public override void RecycleTrack()
- {
- base.RecycleTrack();
-
- trackStore?.Dispose();
- trackStore = null;
- }
-
- public override void TransferTo(WorkingBeatmap other)
- {
- base.TransferTo(other);
-
- if (other is BeatmapManagerWorkingBeatmap owb && textureStore != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo))
- owb.textureStore = textureStore;
- }
-
protected override Waveform GetWaveform()
{
+ if (Metadata?.AudioFile == null)
+ return null;
+
try
{
var trackData = store.GetStream(getPathForFile(Metadata.AudioFile));
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 775d78f1fb..39b3c23ddd 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -6,11 +6,13 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
+using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Users;
namespace osu.Game.Beatmaps
{
+ [ExcludeFromDynamicCompile]
[Serializable]
public class BeatmapMetadata : IEquatable, IHasPrimaryKey
{
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index a8b83dca38..b76d780860 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -5,10 +5,12 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
+using osu.Framework.Testing;
using osu.Game.Database;
namespace osu.Game.Beatmaps
{
+ [ExcludeFromDynamicCompile]
public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable
{
public int ID { get; set; }
diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs
index 0745ec5222..9d87a20d60 100644
--- a/osu.Game/Beatmaps/BeatmapStatistic.cs
+++ b/osu.Game/Beatmaps/BeatmapStatistic.cs
@@ -1,14 +1,31 @@
// 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.Graphics;
using osu.Framework.Graphics.Sprites;
+using osuTK;
namespace osu.Game.Beatmaps
{
public class BeatmapStatistic
{
- public IconUsage Icon;
+ [Obsolete("Use CreateIcon instead")] // can be removed 20210203
+ public IconUsage Icon = FontAwesome.Regular.QuestionCircle;
+
+ ///
+ /// A function to create the icon for display purposes. Use default icons available via whenever possible for conformity.
+ ///
+ public Func CreateIcon;
+
public string Content;
public string Name;
+
+ public BeatmapStatistic()
+ {
+#pragma warning disable 618
+ CreateIcon = () => new SpriteIcon { Icon = Icon, Scale = new Vector2(0.7f) };
+#pragma warning restore 618
+ }
}
}
diff --git a/osu.Game/Beatmaps/BeatmapStatisticIcon.cs b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs
new file mode 100644
index 0000000000..181fb540df
--- /dev/null
+++ b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs
@@ -0,0 +1,43 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using Humanizer;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// A default implementation of an icon used to represent beatmap statistics.
+ ///
+ public class BeatmapStatisticIcon : Sprite
+ {
+ private readonly BeatmapStatisticsIconType iconType;
+
+ public BeatmapStatisticIcon(BeatmapStatisticsIconType iconType)
+ {
+ this.iconType = iconType;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().Kebaberize()}");
+ }
+ }
+
+ public enum BeatmapStatisticsIconType
+ {
+ Accuracy,
+ ApproachRate,
+ Bpm,
+ Circles,
+ HpDrain,
+ Length,
+ OverallDifficulty,
+ Size,
+ Sliders,
+ Spinners,
+ }
+}
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index 8080e94075..af2a2ac250 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -19,7 +20,7 @@ namespace osu.Game.Beatmaps
{
private readonly TextureStore textures;
- public DummyWorkingBeatmap(AudioManager audio, TextureStore textures)
+ public DummyWorkingBeatmap([NotNull] AudioManager audio, TextureStore textures)
: base(new BeatmapInfo
{
Metadata = new BeatmapMetadata
@@ -44,7 +45,7 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4");
- protected override Track GetTrack() => GetVirtualTrack();
+ protected override Track GetBeatmapTrack() => GetVirtualTrack();
private class DummyRulesetInfo : RulesetInfo
{
diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs
index 8f6c7dc328..dba3a37545 100644
--- a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs
+++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs
@@ -8,6 +8,6 @@ namespace osu.Game.Beatmaps.Formats
{
public interface IHasCustomColours
{
- Dictionary CustomColours { get; set; }
+ Dictionary CustomColours { get; }
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 57555cce90..80a4d6dea4 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -7,13 +7,16 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using JetBrains.Annotations;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Skinning;
using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Beatmaps.Formats
{
@@ -23,9 +26,18 @@ namespace osu.Game.Beatmaps.Formats
private readonly IBeatmap beatmap;
- public LegacyBeatmapEncoder(IBeatmap beatmap)
+ [CanBeNull]
+ private readonly ISkin skin;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The beatmap to encode.
+ /// The beatmap's skin, used for encoding combo colours.
+ public LegacyBeatmapEncoder(IBeatmap beatmap, [CanBeNull] ISkin skin)
{
this.beatmap = beatmap;
+ this.skin = skin;
if (beatmap.BeatmapInfo.RulesetID < 0 || beatmap.BeatmapInfo.RulesetID > 3)
throw new ArgumentException("Only beatmaps in the osu, taiko, catch, or mania rulesets can be encoded to the legacy beatmap format.", nameof(beatmap));
@@ -53,6 +65,9 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine();
handleControlPoints(writer);
+ writer.WriteLine();
+ handleColours(writer);
+
writer.WriteLine();
handleHitObjects(writer);
}
@@ -196,6 +211,28 @@ namespace osu.Game.Beatmaps.Formats
}
}
+ private void handleColours(TextWriter writer)
+ {
+ var colours = skin?.GetConfig>(GlobalSkinColours.ComboColours)?.Value;
+
+ if (colours == null || colours.Count == 0)
+ return;
+
+ writer.WriteLine("[Colours]");
+
+ for (var i = 0; i < colours.Count; i++)
+ {
+ var comboColour = colours[i];
+
+ writer.Write(FormattableString.Invariant($"Combo{i}: "));
+ writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},"));
+ writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},"));
+ writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},"));
+ writer.Write(FormattableString.Invariant($"{(byte)(comboColour.A * byte.MaxValue)}"));
+ writer.WriteLine();
+ }
+ }
+
private void handleHitObjects(TextWriter writer)
{
if (beatmap.HitObjects.Count == 0)
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index 44ef9bcacc..c15240a4f6 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -104,10 +104,6 @@ namespace osu.Game.Beatmaps.Formats
try
{
byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255;
-
- if (alpha == 0)
- alpha = 255;
-
colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha);
}
catch
diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs
index 31975157a0..bcd94d76fd 100644
--- a/osu.Game/Beatmaps/IWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs
@@ -26,11 +26,6 @@ namespace osu.Game.Beatmaps
///
Texture Background { get; }
- ///
- /// Retrieves the audio track for this .
- ///
- Track Track { get; }
-
///
/// Retrieves the for the of this .
///
@@ -59,5 +54,18 @@ namespace osu.Game.Beatmaps
/// The converted .
/// If could not be converted to .
IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null);
+
+ ///
+ /// Load a new audio track instance for this beatmap. This should be called once before accessing .
+ /// The caller of this method is responsible for the lifetime of the track.
+ ///
+ ///
+ /// In a standard game context, the loading of the track is managed solely by MusicController, which will
+ /// automatically load the track of the current global IBindable WorkingBeatmap.
+ /// As such, this method should only be called in very special scenarios, such as external tests or apps which are
+ /// outside of the game context.
+ ///
+ /// A fresh track instance, which will also be available via .
+ Track LoadTrack();
}
}
diff --git a/osu.Game/Beatmaps/Legacy/LegacyMods.cs b/osu.Game/Beatmaps/Legacy/LegacyMods.cs
index 583e950e49..0e517ea3df 100644
--- a/osu.Game/Beatmaps/Legacy/LegacyMods.cs
+++ b/osu.Game/Beatmaps/Legacy/LegacyMods.cs
@@ -38,5 +38,6 @@ namespace osu.Game.Beatmaps.Legacy
Key1 = 1 << 26,
Key3 = 1 << 27,
Key2 = 1 << 28,
+ Mirror = 1 << 30,
}
}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index b4bcf285b9..d9780233d1 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -7,11 +7,13 @@ using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using JetBrains.Annotations;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Logging;
using osu.Framework.Statistics;
+using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Types;
@@ -21,6 +23,7 @@ using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
{
+ [ExcludeFromDynamicCompile]
public abstract class WorkingBeatmap : IWorkingBeatmap
{
public readonly BeatmapInfo BeatmapInfo;
@@ -40,7 +43,6 @@ namespace osu.Game.Beatmaps
BeatmapSetInfo = beatmapInfo.BeatmapSet;
Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata();
- track = new RecyclableLazy
public void Stop()
{
- var track = current?.Track;
-
IsUserPaused = true;
- if (track?.IsRunning == true)
- track.Stop();
+ if (CurrentTrack.IsRunning)
+ CurrentTrack.Stop();
}
///
@@ -196,9 +190,7 @@ namespace osu.Game.Overlays
/// Whether the operation was successful.
public bool TogglePause()
{
- var track = current?.Track;
-
- if (track?.IsRunning == true)
+ if (CurrentTrack.IsRunning)
Stop();
else
Play();
@@ -209,7 +201,13 @@ namespace osu.Game.Overlays
///
/// Play the previous track or restart the current track if it's current time below .
///
- public void PreviousTrack() => Schedule(() => prev());
+ /// Invoked when the operation has been performed successfully.
+ public void PreviousTrack(Action onSuccess = null) => Schedule(() =>
+ {
+ PreviousTrackResult res = prev();
+ if (res != PreviousTrackResult.None)
+ onSuccess?.Invoke(res);
+ });
///
/// Play the previous track or restart the current track if it's current time below .
@@ -220,7 +218,7 @@ namespace osu.Game.Overlays
if (beatmap.Disabled)
return PreviousTrackResult.None;
- var currentTrackPosition = current?.Track.CurrentTime;
+ var currentTrackPosition = CurrentTrack.CurrentTime;
if (currentTrackPosition >= restart_cutoff_point)
{
@@ -234,9 +232,7 @@ namespace osu.Game.Overlays
if (playable != null)
{
- if (beatmap is Bindable working)
- working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
-
+ changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
restartTrack();
return PreviousTrackResult.Previous;
}
@@ -247,7 +243,14 @@ namespace osu.Game.Overlays
///
/// Play the next random or playlist track.
///
- public void NextTrack() => Schedule(() => next());
+ /// Invoked when the operation has been performed successfully.
+ /// A of the operation.
+ public void NextTrack(Action onSuccess = null) => Schedule(() =>
+ {
+ bool res = next();
+ if (res)
+ onSuccess?.Invoke();
+ });
private bool next()
{
@@ -260,9 +263,7 @@ namespace osu.Game.Overlays
if (playable != null)
{
- if (beatmap is Bindable working)
- working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
-
+ changeBeatmap(beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value));
restartTrack();
return true;
}
@@ -274,21 +275,30 @@ namespace osu.Game.Overlays
{
// if not scheduled, the previously track will be stopped one frame later (see ScheduleAfterChildren logic in GameBase).
// we probably want to move this to a central method for switching to a new working beatmap in the future.
- Schedule(() => beatmap.Value.Track.Restart());
+ Schedule(() => CurrentTrack.Restart());
}
private WorkingBeatmap current;
private TrackChangeDirection? queuedDirection;
- private void beatmapChanged(ValueChangedEvent beatmap)
+ private void beatmapChanged(ValueChangedEvent beatmap) => changeBeatmap(beatmap.NewValue);
+
+ private void changeBeatmap(WorkingBeatmap newWorking)
{
+ // This method can potentially be triggered multiple times as it is eagerly fired in next() / prev() to ensure correct execution order
+ // (changeBeatmap must be called before consumers receive the bindable changed event, which is not the case when the local beatmap bindable is updated directly).
+ if (newWorking == current)
+ return;
+
+ var lastWorking = current;
+
TrackChangeDirection direction = TrackChangeDirection.None;
+ bool audioEquals = newWorking?.BeatmapInfo?.AudioEquals(current?.BeatmapInfo) ?? false;
+
if (current != null)
{
- bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false;
-
if (audioEquals)
direction = TrackChangeDirection.None;
else if (queuedDirection.HasValue)
@@ -300,18 +310,74 @@ namespace osu.Game.Overlays
{
// figure out the best direction based on order in playlist.
var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
- var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
+ var next = newWorking == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != newWorking.BeatmapSetInfo?.ID).Count();
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
}
}
- current = beatmap.NewValue;
+ current = newWorking;
+
+ if (!audioEquals || CurrentTrack.IsDummyDevice)
+ {
+ changeTrack();
+ }
+ else
+ {
+ // transfer still valid track to new working beatmap
+ current.TransferTrack(lastWorking.Track);
+ }
+
TrackChanged?.Invoke(current, direction);
ResetTrackAdjustments();
queuedDirection = null;
+
+ // this will be a noop if coming from the beatmapChanged event.
+ // the exception is local operations like next/prev, where we want to complete loading the track before sending out a change.
+ if (beatmap.Value != current && beatmap is Bindable working)
+ working.Value = current;
+ }
+
+ private void changeTrack()
+ {
+ var lastTrack = CurrentTrack;
+
+ var queuedTrack = new DrawableTrack(current.LoadTrack());
+ queuedTrack.Completed += () => onTrackCompleted(current);
+
+ CurrentTrack = queuedTrack;
+
+ // At this point we may potentially be in an async context from tests. This is extremely dangerous but we have to make do for now.
+ // CurrentTrack is immediately updated above for situations where a immediate knowledge about the new track is required,
+ // but the mutation of the hierarchy is scheduled to avoid exceptions.
+ Schedule(() =>
+ {
+ lastTrack.VolumeTo(0, 500, Easing.Out).Expire();
+
+ if (queuedTrack == CurrentTrack)
+ {
+ AddInternal(queuedTrack);
+ queuedTrack.VolumeTo(0).Then().VolumeTo(1, 300, Easing.Out);
+ }
+ else
+ {
+ // If the track has changed since the call to changeTrack, it is safe to dispose the
+ // queued track rather than consume it.
+ queuedTrack.Dispose();
+ }
+ });
+ }
+
+ private void onTrackCompleted(WorkingBeatmap workingBeatmap)
+ {
+ // the source of track completion is the audio thread, so the beatmap may have changed before firing.
+ if (current != workingBeatmap)
+ return;
+
+ if (!CurrentTrack.Looping && !beatmap.Disabled)
+ NextTrack();
}
private bool allowRateAdjustments;
@@ -332,66 +398,20 @@ namespace osu.Game.Overlays
}
}
+ ///
+ /// Resets the speed adjustments currently applied on and applies the mod adjustments if is true.
+ ///
+ ///
+ /// Does not reset speed adjustments applied directly to the beatmap track.
+ ///
public void ResetTrackAdjustments()
{
- var track = current?.Track;
- if (track == null)
- return;
-
- track.ResetSpeedAdjustments();
+ CurrentTrack.ResetSpeedAdjustments();
if (allowRateAdjustments)
{
foreach (var mod in mods.Value.OfType())
- mod.ApplyToTrack(track);
- }
- }
-
- public bool OnPressed(GlobalAction action)
- {
- if (beatmap.Disabled)
- return false;
-
- switch (action)
- {
- case GlobalAction.MusicPlay:
- if (TogglePause())
- onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track"));
- return true;
-
- case GlobalAction.MusicNext:
- if (next())
- onScreenDisplay?.Display(new MusicControllerToast("Next track"));
-
- return true;
-
- case GlobalAction.MusicPrev:
- switch (prev())
- {
- case PreviousTrackResult.Restart:
- onScreenDisplay?.Display(new MusicControllerToast("Restart track"));
- break;
-
- case PreviousTrackResult.Previous:
- onScreenDisplay?.Display(new MusicControllerToast("Previous track"));
- break;
- }
-
- return true;
- }
-
- return false;
- }
-
- public void OnReleased(GlobalAction action)
- {
- }
-
- public class MusicControllerToast : Toast
- {
- public MusicControllerToast(string action)
- : base("Music Playback", action, string.Empty)
- {
+ mod.ApplyToTrack(CurrentTrack);
}
}
}
diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs
index ddada2bdaf..63174128e7 100644
--- a/osu.Game/Overlays/News/NewsHeader.cs
+++ b/osu.Game/Overlays/News/NewsHeader.cs
@@ -57,7 +57,8 @@ namespace osu.Game.Overlays.News
public NewsHeaderTitle()
{
Title = "news";
- IconTexture = "Icons/news";
+ Description = "get up-to-date on community happenings";
+ IconTexture = "Icons/Hexacons/news";
}
}
}
diff --git a/osu.Game/Overlays/NewsOverlay.cs b/osu.Game/Overlays/NewsOverlay.cs
index 09fb445b1f..c8c1db012f 100644
--- a/osu.Game/Overlays/NewsOverlay.cs
+++ b/osu.Game/Overlays/NewsOverlay.cs
@@ -13,17 +13,16 @@ using osu.Game.Overlays.News.Displays;
namespace osu.Game.Overlays
{
- public class NewsOverlay : FullscreenOverlay
+ public class NewsOverlay : FullscreenOverlay
{
private readonly Bindable article = new Bindable(null);
private Container content;
private LoadingLayer loading;
- private NewsHeader header;
private OverlayScrollContainer scrollFlow;
public NewsOverlay()
- : base(OverlayColourScheme.Purple)
+ : base(OverlayColourScheme.Purple, new NewsHeader())
{
}
@@ -48,10 +47,10 @@ namespace osu.Game.Overlays
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- header = new NewsHeader
+ Header.With(h =>
{
- ShowFrontPage = ShowFrontPage
- },
+ h.ShowFrontPage = ShowFrontPage;
+ }),
content = new Container
{
RelativeSizeAxes = Axes.X,
@@ -112,12 +111,12 @@ namespace osu.Game.Overlays
if (e.NewValue == null)
{
- header.SetFrontPage();
+ Header.SetFrontPage();
LoadDisplay(new FrontPageDisplay());
return;
}
- header.SetArticle(e.NewValue);
+ Header.SetArticle(e.NewValue);
LoadDisplay(Empty());
}
diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs
index 41160d10ec..b5714fbcae 100644
--- a/osu.Game/Overlays/NotificationOverlay.cs
+++ b/osu.Game/Overlays/NotificationOverlay.cs
@@ -16,8 +16,12 @@ using osu.Framework.Threading;
namespace osu.Game.Overlays
{
- public class NotificationOverlay : OsuFocusedOverlayContainer
+ public class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
{
+ public string IconTexture => "Icons/Hexacons/notification";
+ public string Title => "notifications";
+ public string Description => "waiting for 'ya";
+
private const float width = 320;
public const float TRANSITION_LENGTH = 600;
diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs
index ebb4a96d14..55adf02a45 100644
--- a/osu.Game/Overlays/NowPlayingOverlay.cs
+++ b/osu.Game/Overlays/NowPlayingOverlay.cs
@@ -25,8 +25,12 @@ using osuTK.Graphics;
namespace osu.Game.Overlays
{
- public class NowPlayingOverlay : OsuFocusedOverlayContainer
+ public class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent
{
+ public string IconTexture => "Icons/Hexacons/music";
+ public string Title => "now playing";
+ public string Description => "manage the currently playing track";
+
private const float player_height = 130;
private const float transition_length = 800;
private const float progress_height = 10;
@@ -234,9 +238,9 @@ namespace osu.Game.Overlays
pendingBeatmapSwitch = null;
}
- var track = beatmap.Value?.TrackLoaded ?? false ? beatmap.Value.Track : null;
+ var track = musicController.CurrentTrack;
- if (track?.IsDummyDevice == false)
+ if (!track.IsDummyDevice)
{
progressBar.EndTime = track.Length;
progressBar.CurrentTime = track.CurrentTime;
diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs
index cc7f798c4a..fed1e57686 100644
--- a/osu.Game/Overlays/OverlayHeader.cs
+++ b/osu.Game/Overlays/OverlayHeader.cs
@@ -12,6 +12,8 @@ namespace osu.Game.Overlays
{
public abstract class OverlayHeader : Container
{
+ public OverlayTitle Title { get; }
+
private float contentSidePadding;
///
@@ -73,7 +75,7 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.Y,
Children = new[]
{
- CreateTitle().With(title =>
+ Title = CreateTitle().With(title =>
{
title.Anchor = Anchor.CentreLeft;
title.Origin = Anchor.CentreLeft;
diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs
index e7415e6f74..b67d5db1a4 100644
--- a/osu.Game/Overlays/OverlayScrollContainer.cs
+++ b/osu.Game/Overlays/OverlayScrollContainer.cs
@@ -17,7 +17,7 @@ using osuTK.Graphics;
namespace osu.Game.Overlays
{
///
- /// which provides . Mostly used in .
+ /// which provides . Mostly used in .
///
public class OverlayScrollContainer : OsuScrollContainer
{
diff --git a/osu.Game/Overlays/OverlayTitle.cs b/osu.Game/Overlays/OverlayTitle.cs
index 1c9567428c..17eeece1f8 100644
--- a/osu.Game/Overlays/OverlayTitle.cs
+++ b/osu.Game/Overlays/OverlayTitle.cs
@@ -12,19 +12,27 @@ using osuTK;
namespace osu.Game.Overlays
{
- public abstract class OverlayTitle : CompositeDrawable
+ public abstract class OverlayTitle : CompositeDrawable, INamedOverlayComponent
{
- private readonly OsuSpriteText title;
+ private readonly OsuSpriteText titleText;
private readonly Container icon;
- protected string Title
+ private string title;
+
+ public string Title
{
- set => title.Text = value;
+ get => title;
+ protected set => titleText.Text = title = value;
}
- protected string IconTexture
+ public string Description { get; protected set; }
+
+ private string iconTexture;
+
+ public string IconTexture
{
- set => icon.Child = new OverlayTitleIcon(value);
+ get => iconTexture;
+ protected set => icon.Child = new OverlayTitleIcon(iconTexture = value);
}
protected OverlayTitle()
@@ -45,7 +53,7 @@ namespace osu.Game.Overlays
Margin = new MarginPadding { Horizontal = 5 }, // compensates for osu-web sprites having around 5px of whitespace on each side
Size = new Vector2(30)
},
- title = new OsuSpriteText
+ titleText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game/Overlays/OverlayView.cs b/osu.Game/Overlays/OverlayView.cs
index 3e2c54c726..312271316a 100644
--- a/osu.Game/Overlays/OverlayView.cs
+++ b/osu.Game/Overlays/OverlayView.cs
@@ -9,7 +9,7 @@ using osu.Game.Online.API;
namespace osu.Game.Overlays
{
///
- /// A subview containing online content, to be displayed inside a .
+ /// A subview containing online content, to be displayed inside a .
///
///
/// Automatically performs a data fetch on load.
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 55474c9d3e..c947ef0781 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Profile
public ProfileHeaderTitle()
{
Title = "player info";
- IconTexture = "Icons/profile";
+ IconTexture = "Icons/Hexacons/profile";
}
}
diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs
index e30c6f07a8..92e22f5873 100644
--- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs
+++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs
@@ -30,7 +30,8 @@ namespace osu.Game.Overlays.Rankings
public RankingsTitle()
{
Title = "ranking";
- IconTexture = "Icons/rankings";
+ Description = "find out who's the best right now";
+ IconTexture = "Icons/Hexacons/rankings";
}
}
}
diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs
index 7b200d4226..ae6d49960a 100644
--- a/osu.Game/Overlays/RankingsOverlay.cs
+++ b/osu.Game/Overlays/RankingsOverlay.cs
@@ -17,17 +17,16 @@ using osu.Game.Overlays.Rankings.Tables;
namespace osu.Game.Overlays
{
- public class RankingsOverlay : FullscreenOverlay
+ public class RankingsOverlay : FullscreenOverlay
{
- protected Bindable Country => header.Country;
+ protected Bindable Country => Header.Country;
- protected Bindable Scope => header.Current;
+ protected Bindable Scope => Header.Current;
private readonly OverlayScrollContainer scrollFlow;
private readonly Container contentContainer;
private readonly LoadingLayer loading;
private readonly Box background;
- private readonly RankingsOverlayHeader header;
private APIRequest lastRequest;
private CancellationTokenSource cancellationToken;
@@ -36,7 +35,12 @@ namespace osu.Game.Overlays
private IAPIProvider api { get; set; }
public RankingsOverlay()
- : base(OverlayColourScheme.Green)
+ : base(OverlayColourScheme.Green, new RankingsOverlayHeader
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Depth = -float.MaxValue
+ })
{
Children = new Drawable[]
{
@@ -55,12 +59,7 @@ namespace osu.Game.Overlays
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- header = new RankingsOverlayHeader
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Depth = -float.MaxValue
- },
+ Header,
new Container
{
RelativeSizeAxes = Axes.X,
@@ -97,7 +96,7 @@ namespace osu.Game.Overlays
{
base.LoadComplete();
- header.Ruleset.BindTo(ruleset);
+ Header.Ruleset.BindTo(ruleset);
Country.BindValueChanged(_ =>
{
diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs
index e0163b5b0c..1990674aa9 100644
--- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs
+++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs
@@ -36,7 +36,7 @@ namespace osu.Game.Overlays.SearchableList
///
/// The amount of padding added to content (does not affect background or tab control strip).
///
- protected virtual float ContentHorizontalPadding => SearchableListOverlay.WIDTH_PADDING;
+ protected virtual float ContentHorizontalPadding => WaveOverlayContainer.WIDTH_PADDING;
protected SearchableListFilterControl()
{
diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs
deleted file mode 100644
index 66fedf0a56..0000000000
--- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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 osuTK;
-using osuTK.Graphics;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
-
-namespace osu.Game.Overlays.SearchableList
-{
- public abstract class SearchableListHeader : Container
- where T : struct, Enum
- {
- public readonly HeaderTabControl Tabs;
-
- protected abstract Color4 BackgroundColour { get; }
- protected abstract T DefaultTab { get; }
- protected abstract Drawable CreateHeaderText();
- protected abstract IconUsage Icon { get; }
-
- protected SearchableListHeader()
- {
- RelativeSizeAxes = Axes.X;
- Height = 90;
-
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = BackgroundColour,
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING, Right = SearchableListOverlay.WIDTH_PADDING },
- Children = new Drawable[]
- {
- new FillFlowContainer
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.BottomLeft,
- Position = new Vector2(-35f, 5f),
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(10f, 0f),
- Children = new[]
- {
- new SpriteIcon
- {
- Size = new Vector2(25),
- Icon = Icon,
- },
- CreateHeaderText(),
- },
- },
- Tabs = new HeaderTabControl
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- },
- },
- },
- };
-
- Tabs.Current.Value = DefaultTab;
- Tabs.Current.TriggerChange();
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- Tabs.StripColour = colours.Green;
- }
- }
-}
diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs
deleted file mode 100644
index 4ab2de06b6..0000000000
--- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-// 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 osuTK.Graphics;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input.Events;
-using osu.Game.Graphics.Backgrounds;
-using osu.Game.Graphics.Cursor;
-
-namespace osu.Game.Overlays.SearchableList
-{
- public abstract class SearchableListOverlay : FullscreenOverlay
- {
- public const float WIDTH_PADDING = 80;
-
- protected SearchableListOverlay(OverlayColourScheme colourScheme)
- : base(colourScheme)
- {
- }
- }
-
- public abstract class SearchableListOverlay : SearchableListOverlay
- where THeader : struct, Enum
- where TTab : struct, Enum
- where TCategory : struct, Enum
- {
- private readonly Container scrollContainer;
-
- protected readonly SearchableListHeader Header;
- protected readonly SearchableListFilterControl Filter;
- protected readonly FillFlowContainer ScrollFlow;
-
- protected abstract Color4 BackgroundColour { get; }
- protected abstract Color4 TrianglesColourLight { get; }
- protected abstract Color4 TrianglesColourDark { get; }
- protected abstract SearchableListHeader CreateHeader();
- protected abstract SearchableListFilterControl CreateFilterControl();
-
- protected SearchableListOverlay(OverlayColourScheme colourScheme)
- : base(colourScheme)
- {
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = BackgroundColour,
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Children = new[]
- {
- new Triangles
- {
- RelativeSizeAxes = Axes.Both,
- TriangleScale = 5,
- ColourLight = TrianglesColourLight,
- ColourDark = TrianglesColourDark,
- },
- },
- },
- scrollContainer = new Container
- {
- RelativeSizeAxes = Axes.Both,
- Child = new OsuContextMenuContainer
- {
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Child = new OverlayScrollContainer
- {
- RelativeSizeAxes = Axes.Both,
- ScrollbarVisible = false,
- Child = ScrollFlow = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Padding = new MarginPadding { Horizontal = 10, Bottom = 50 },
- Direction = FillDirection.Vertical,
- },
- },
- },
- },
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- Header = CreateHeader(),
- Filter = CreateFilterControl(),
- },
- },
- };
- }
-
- protected override void Update()
- {
- base.Update();
-
- scrollContainer.Padding = new MarginPadding { Top = Header.Height + Filter.Height };
- }
-
- protected override void OnFocus(FocusEvent e)
- {
- Filter.Search.TakeFocus();
- }
-
- protected override void PopIn()
- {
- base.PopIn();
-
- Filter.Search.HoldFocus = true;
- }
-
- protected override void PopOut()
- {
- base.PopOut();
-
- Filter.Search.HoldFocus = false;
- }
- }
-}
diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
index aca507f20a..e5cebd28e2 100644
--- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
@@ -1,12 +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.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Overlays.Settings.Sections.Gameplay;
using osu.Game.Rulesets;
using System.Linq;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Logging;
namespace osu.Game.Overlays.Settings.Sections
{
@@ -34,9 +36,17 @@ namespace osu.Game.Overlays.Settings.Sections
{
foreach (Ruleset ruleset in rulesets.AvailableRulesets.Select(info => info.CreateInstance()))
{
- SettingsSubsection section = ruleset.CreateSettings();
- if (section != null)
- Add(section);
+ try
+ {
+ SettingsSubsection section = ruleset.CreateSettings();
+
+ if (section != null)
+ Add(section);
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e, "Failed to load ruleset settings");
+ }
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 00b7643332..4312b319c0 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -163,8 +163,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything);
}, true);
- windowModes.ItemsAdded += _ => windowModesChanged();
- windowModes.ItemsRemoved += _ => windowModesChanged();
+ windowModes.CollectionChanged += (sender, args) => windowModesChanged();
windowModesChanged();
}
diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs
index 358f94b659..031ecaae46 100644
--- a/osu.Game/Overlays/Settings/Sidebar.cs
+++ b/osu.Game/Overlays/Settings/Sidebar.cs
@@ -4,22 +4,21 @@
using System;
using System.Linq;
using osu.Framework;
-using osuTK;
-using osuTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Threading;
using osu.Game.Graphics.Containers;
-using osu.Game.Overlays.Toolbar;
+using osuTK;
+using osuTK.Graphics;
namespace osu.Game.Overlays.Settings
{
public class Sidebar : Container, IStateful
{
private readonly FillFlowContainer content;
- public const float DEFAULT_WIDTH = ToolbarButton.WIDTH;
+ public const float DEFAULT_WIDTH = Toolbar.Toolbar.HEIGHT * 1.4f;
public const int EXPANDED_WIDTH = 200;
public event Action StateChanged;
diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs
index bb84de5d3a..e1bcdbbaf0 100644
--- a/osu.Game/Overlays/SettingsOverlay.cs
+++ b/osu.Game/Overlays/SettingsOverlay.cs
@@ -13,8 +13,12 @@ using osu.Framework.Bindables;
namespace osu.Game.Overlays
{
- public class SettingsOverlay : SettingsPanel
+ public class SettingsOverlay : SettingsPanel, INamedOverlayComponent
{
+ public string IconTexture => "Icons/Hexacons/settings";
+ public string Title => "settings";
+ public string Description => "change the way osu! behaves";
+
protected override IEnumerable CreateSections() => new SettingsSection[]
{
new GeneralSection(),
@@ -30,7 +34,7 @@ namespace osu.Game.Overlays
private readonly List subPanels = new List();
- protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves");
+ protected override Drawable CreateHeader() => new SettingsHeader(Title, Description);
protected override Drawable CreateFooter() => new SettingsFooter();
public SettingsOverlay()
diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs
index 3bf9e85428..393e349bd0 100644
--- a/osu.Game/Overlays/Toolbar/Toolbar.cs
+++ b/osu.Game/Overlays/Toolbar/Toolbar.cs
@@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Toolbar
private const double transition_time = 500;
- protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All);
+ protected readonly IBindable OverlayActivationMode = new Bindable(OverlayActivation.All);
// Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden.
public override bool PropagateNonPositionalInputSubTree => true;
diff --git a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
index cde305fffd..0363873326 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarBeatmapListingButton.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Game.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
@@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar
{
public ToolbarBeatmapListingButton()
{
- SetIcon(OsuIcon.ChevronDownCircle);
- TooltipMain = "Beatmap listing";
- TooltipSub = "Browse for new beatmaps";
-
Hotkey = GlobalAction.ToggleDirect;
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
index 0afc6642b2..49b9c62d85 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs
@@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
@@ -25,8 +26,6 @@ namespace osu.Game.Overlays.Toolbar
{
public abstract class ToolbarButton : OsuClickableContainer, IKeyBindingHandler
{
- public const float WIDTH = Toolbar.HEIGHT * 1.4f;
-
protected GlobalAction? Hotkey { get; set; }
public void SetIcon(Drawable icon)
@@ -35,16 +34,14 @@ namespace osu.Game.Overlays.Toolbar
IconContainer.Show();
}
- public void SetIcon(IconUsage icon) => SetIcon(new SpriteIcon
- {
- Size = new Vector2(20),
- Icon = icon
- });
+ [Resolved]
+ private TextureStore textures { get; set; }
- public IconUsage Icon
- {
- set => SetIcon(value);
- }
+ public void SetIcon(string texture) =>
+ SetIcon(new Sprite
+ {
+ Texture = textures.Get(texture),
+ });
public string Text
{
@@ -82,7 +79,7 @@ namespace osu.Game.Overlays.Toolbar
protected ToolbarButton()
: base(HoverSampleSet.Loud)
{
- Width = WIDTH;
+ Width = Toolbar.HEIGHT;
RelativeSizeAxes = Axes.Y;
Children = new Drawable[]
@@ -116,7 +113,7 @@ namespace osu.Game.Overlays.Toolbar
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Size = new Vector2(20),
+ Size = new Vector2(26),
Alpha = 0,
},
DrawableText = new OsuSpriteText
diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
index c88b418853..23f8b141b2 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs
@@ -2,19 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarChangelogButton : ToolbarOverlayToggleButton
{
- public ToolbarChangelogButton()
- {
- SetIcon(FontAwesome.Solid.Bullhorn);
- TooltipMain = "Changelog";
- TooltipSub = "Track recent dev updates in the osu! ecosystem";
- }
-
[BackgroundDependencyLoader(true)]
private void load(ChangelogOverlay changelog)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
index dee4be0c1f..f9a66ae7bb 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
@@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar
{
public ToolbarChatButton()
{
- SetIcon(FontAwesome.Solid.Comments);
- TooltipMain = "Chat";
- TooltipSub = "Join the real-time discussion";
-
Hotkey = GlobalAction.ToggleChat;
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs
index 4845c9a99f..76fbd40d66 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.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.Graphics.Sprites;
+using osu.Framework.Allocation;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
@@ -10,11 +10,16 @@ namespace osu.Game.Overlays.Toolbar
{
public ToolbarHomeButton()
{
- Icon = FontAwesome.Solid.Home;
- TooltipMain = "Home";
- TooltipSub = "Return to the main menu";
-
+ Width *= 1.4f;
Hotkey = GlobalAction.Home;
}
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ TooltipMain = "home";
+ TooltipSub = "return to the main menu";
+ SetIcon("Icons/Hexacons/home");
+ }
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs
index f9aa2de497..0f5e8e5456 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs
@@ -3,7 +3,6 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
@@ -14,10 +13,6 @@ namespace osu.Game.Overlays.Toolbar
public ToolbarMusicButton()
{
- Icon = FontAwesome.Solid.Music;
- TooltipMain = "Now playing";
- TooltipSub = "Manage the currently playing track";
-
Hotkey = GlobalAction.ToggleNowPlaying;
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
index 106c67a041..0ba2935c80 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarNewsButton.cs
@@ -2,19 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarNewsButton : ToolbarOverlayToggleButton
{
- public ToolbarNewsButton()
- {
- Icon = FontAwesome.Solid.Newspaper;
- TooltipMain = "News";
- TooltipSub = "Get up-to-date on community happenings";
- }
-
[BackgroundDependencyLoader(true)]
private void load(NewsOverlay news)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs
index a699fd907f..79d0fc74c1 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs
@@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
@@ -25,10 +24,6 @@ namespace osu.Game.Overlays.Toolbar
public ToolbarNotificationButton()
{
- Icon = FontAwesome.Solid.Bars;
- TooltipMain = "Notifications";
- TooltipSub = "Waiting for 'ya";
-
Hotkey = GlobalAction.ToggleNotifications;
Add(countDisplay = new CountCircle
diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs
index 36387bb00d..0dea71cc08 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs
@@ -32,6 +32,13 @@ namespace osu.Game.Overlays.Toolbar
Action = stateContainer.ToggleVisibility;
overlayState.BindTo(stateContainer.State);
}
+
+ if (stateContainer is INamedOverlayComponent named)
+ {
+ TooltipMain = named.Title;
+ TooltipSub = named.Description;
+ SetIcon(named.IconTexture);
+ }
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
index c026ce99fe..22a01bcdb5 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarRankingsButton.cs
@@ -2,19 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarRankingsButton : ToolbarOverlayToggleButton
{
- public ToolbarRankingsButton()
- {
- SetIcon(FontAwesome.Regular.ChartBar);
- TooltipMain = "Ranking";
- TooltipSub = "Find out who's the best right now";
- }
-
[BackgroundDependencyLoader(true)]
private void load(RankingsOverlay rankings)
{
diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs
index 422bf00c6d..905d5b44c6 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Toolbar
},
ModeButtonLine = new Container
{
- Size = new Vector2(ToolbarButton.WIDTH, 3),
+ Size = new Vector2(Toolbar.HEIGHT, 3),
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
Masking = true,
diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs
index a5194ea752..564fd65719 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Toolbar
var rInstance = value.CreateInstance();
ruleset.TooltipMain = rInstance.Description;
- ruleset.TooltipSub = $"Play some {rInstance.Description}";
+ ruleset.TooltipSub = $"play some {rInstance.Description}";
ruleset.SetIcon(rInstance.CreateIcon());
}
@@ -65,12 +65,6 @@ namespace osu.Game.Overlays.Toolbar
Parent.Click();
return base.OnClick(e);
}
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- IconContainer.Scale *= 1.4f;
- }
}
}
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs
index ed2a23ec2a..c53f4a55d9 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
@@ -11,10 +10,7 @@ namespace osu.Game.Overlays.Toolbar
{
public ToolbarSettingsButton()
{
- Icon = FontAwesome.Solid.Cog;
- TooltipMain = "Settings";
- TooltipSub = "Change your settings";
-
+ Width *= 1.4f;
Hotkey = GlobalAction.ToggleSettings;
}
diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
index 6faa58c559..e62c7bc807 100644
--- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
+++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar
@@ -11,10 +10,6 @@ namespace osu.Game.Overlays.Toolbar
{
public ToolbarSocialButton()
{
- Icon = FontAwesome.Solid.Users;
- TooltipMain = "Friends";
- TooltipSub = "Interact with those close to you";
-
Hotkey = GlobalAction.ToggleSocial;
}
diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs
index b4c8a2d3ca..2b316c0e34 100644
--- a/osu.Game/Overlays/UserProfileOverlay.cs
+++ b/osu.Game/Overlays/UserProfileOverlay.cs
@@ -17,19 +17,18 @@ using osuTK;
namespace osu.Game.Overlays
{
- public class UserProfileOverlay : FullscreenOverlay
+ public class UserProfileOverlay : FullscreenOverlay
{
private ProfileSection lastSection;
private ProfileSection[] sections;
private GetUserRequest userReq;
- protected ProfileHeader Header;
private ProfileSectionsContainer sectionsContainer;
private ProfileSectionTabControl tabs;
public const float CONTENT_X_MARGIN = 70;
public UserProfileOverlay()
- : base(OverlayColourScheme.Pink)
+ : base(OverlayColourScheme.Pink, new ProfileHeader())
{
}
@@ -45,6 +44,9 @@ namespace osu.Game.Overlays
if (user.Id == Header?.User.Value?.Id)
return;
+ if (sectionsContainer != null)
+ sectionsContainer.ExpandableHeader = null;
+
userReq?.Cancel();
Clear();
lastSection = null;
@@ -77,7 +79,7 @@ namespace osu.Game.Overlays
Add(sectionsContainer = new ProfileSectionsContainer
{
- ExpandableHeader = Header = new ProfileHeader(),
+ ExpandableHeader = Header,
FixedHeader = tabs,
HeaderBackground = new Box
{
diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs
index 5c87096dd4..d0fa9987d5 100644
--- a/osu.Game/Overlays/WaveOverlayContainer.cs
+++ b/osu.Game/Overlays/WaveOverlayContainer.cs
@@ -14,6 +14,8 @@ namespace osu.Game.Overlays
protected override bool BlockNonPositionalInput => true;
protected override Container Content => Waves;
+ public const float WIDTH_PADDING = 80;
+
protected override bool StartHidden => true;
protected WaveOverlayContainer()
diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs
index 4d6d958e82..9b840cea08 100644
--- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs
+++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs
@@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mods
///
public interface IApplicableToTrack : IApplicableMod
{
- void ApplyToTrack(Track track);
+ void ApplyToTrack(ITrack track);
}
}
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index 52ffa0ad2a..b8dc7a2661 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -9,6 +9,7 @@ using System.Reflection;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.UI;
@@ -18,6 +19,7 @@ namespace osu.Game.Rulesets.Mods
///
/// The base class for gameplay modifiers.
///
+ [ExcludeFromDynamicCompile]
public abstract class Mod : IMod, IJsonSerializable
{
///
diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs
index bd98e735e5..61ad7db706 100644
--- a/osu.Game/Rulesets/Mods/ModDaycore.cs
+++ b/osu.Game/Rulesets/Mods/ModDaycore.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods
}, true);
}
- public override void ApplyToTrack(Track track)
+ public override void ApplyToTrack(ITrack track)
{
// base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs
index ed8eb2fb66..4004953cd1 100644
--- a/osu.Game/Rulesets/Mods/ModNightcore.cs
+++ b/osu.Game/Rulesets/Mods/ModNightcore.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mods
}, true);
}
- public override void ApplyToTrack(Track track)
+ public override void ApplyToTrack(ITrack track)
{
// base.ApplyToTrack() intentionally not called (different tempo adjustment is applied)
track.AddAdjustment(AdjustableProperty.Frequency, freqAdjust);
diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
index 874384686f..fec21764b0 100644
--- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs
@@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Mods
{
public abstract BindableNumber SpeedChange { get; }
- public virtual void ApplyToTrack(Track track)
+ public virtual void ApplyToTrack(ITrack track)
{
track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange);
}
diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
index 839d97f04e..20c8d0f3e7 100644
--- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs
+++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mods
Precision = 0.01,
};
- private Track track;
+ private ITrack track;
protected ModTimeRamp()
{
@@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Mods
AdjustPitch.BindValueChanged(applyPitchAdjustment);
}
- public void ApplyToTrack(Track track)
+ public void ApplyToTrack(ITrack track)
{
this.track = track;
diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs
index 3a7f433a37..915544d010 100644
--- a/osu.Game/Rulesets/Ruleset.cs
+++ b/osu.Game/Rulesets/Ruleset.cs
@@ -23,10 +23,12 @@ using osu.Game.Scoring;
using osu.Game.Skinning;
using osu.Game.Users;
using JetBrains.Annotations;
+using osu.Framework.Testing;
using osu.Game.Screens.Ranking.Statistics;
namespace osu.Game.Rulesets
{
+ [ExcludeFromDynamicCompile]
public abstract class Ruleset
{
public RulesetInfo RulesetInfo { get; internal set; }
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index 2e32b96084..d5aca8c650 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -5,9 +5,11 @@ using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Newtonsoft.Json;
+using osu.Framework.Testing;
namespace osu.Game.Rulesets
{
+ [ExcludeFromDynamicCompile]
public class RulesetInfo : IEquatable
{
public int? ID { get; set; }
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index a4a560c8e4..97cb5ca7ab 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -13,7 +13,6 @@ using osu.Game.Replays.Legacy;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Replays;
-using osu.Game.Rulesets.Scoring;
using osu.Game.Users;
using SharpCompress.Compressors.LZMA;
@@ -123,12 +122,12 @@ namespace osu.Game.Scoring.Legacy
protected void CalculateAccuracy(ScoreInfo score)
{
- score.Statistics.TryGetValue(HitResult.Miss, out int countMiss);
- score.Statistics.TryGetValue(HitResult.Meh, out int count50);
- score.Statistics.TryGetValue(HitResult.Good, out int count100);
- score.Statistics.TryGetValue(HitResult.Great, out int count300);
- score.Statistics.TryGetValue(HitResult.Perfect, out int countGeki);
- score.Statistics.TryGetValue(HitResult.Ok, out int countKatu);
+ int countMiss = score.GetCountMiss() ?? 0;
+ int count50 = score.GetCount50() ?? 0;
+ int count100 = score.GetCount100() ?? 0;
+ int count300 = score.GetCount300() ?? 0;
+ int countGeki = score.GetCountGeki() ?? 0;
+ int countKatu = score.GetCountKatu() ?? 0;
switch (score.Ruleset.ID)
{
@@ -241,12 +240,15 @@ namespace osu.Game.Scoring.Legacy
}
var diff = Parsing.ParseFloat(split[0]);
+ var mouseX = Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE);
+ var mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE);
lastTime += diff;
- if (i == 0 && diff == 0)
- // osu-stable adds a zero-time frame before potentially valid negative user frames.
- // we need to ignore this.
+ if (i < 2 && mouseX == 256 && mouseY == -500)
+ // at the start of the replay, stable places two replay frames, at time 0 and SkipBoundary - 1, respectively.
+ // both frames use a position of (256, -500).
+ // ignore these frames as they serve no real purpose (and can even mislead ruleset-specific handlers - see mania)
continue;
// Todo: At some point we probably want to rewind and play back the negative-time frames
@@ -255,8 +257,8 @@ namespace osu.Game.Scoring.Legacy
continue;
currentFrame = convertFrame(new LegacyReplayFrame(lastTime,
- Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE),
- Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE),
+ mouseX,
+ mouseY,
(ReplayButtonState)Parsing.ParseInt(split[3])), currentFrame);
replay.Frames.Add(currentFrame);
diff --git a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs
index 089da4f222..b8bc5cdf36 100644
--- a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs
+++ b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs
@@ -32,8 +32,6 @@ namespace osu.Game.Screens.Edit.Components.Menus
Height = 1,
Colour = Color4.White.Opacity(0.2f),
});
-
- Current.Value = EditorScreenMode.Compose;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
index fcff672045..865e225645 100644
--- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
AddRangeInternal(new[]
{
- DragBox = CreateDragBox(select),
+ DragBox = CreateDragBox(selectBlueprintsFromDragRectangle),
selectionHandler,
SelectionBlueprints = CreateSelectionBlueprintContainer(),
selectionHandler.CreateProxy(),
@@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// Select all masks in a given rectangle selection area.
///
/// The rectangle to perform a selection on in screen-space coordinates.
- private void select(RectangleF rect)
+ private void selectBlueprintsFromDragRectangle(RectangleF rect)
{
foreach (var blueprint in SelectionBlueprints)
{
diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
index 717d60b4f3..ed3d328330 100644
--- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
+++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs
@@ -26,6 +26,28 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved]
private EditorClock editorClock { get; set; }
+ ///
+ /// The timeline's scroll position in the last frame.
+ ///
+ private float lastScrollPosition;
+
+ ///
+ /// The track time in the last frame.
+ ///
+ private double lastTrackTime;
+
+ ///
+ /// Whether the user is currently dragging the timeline.
+ ///
+ private bool handlingDragInput;
+
+ ///
+ /// Whether the track was playing before a user drag event.
+ ///
+ private bool trackWasPlaying;
+
+ private Track track;
+
public Timeline()
{
ZoomDuration = 200;
@@ -59,6 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
waveform.Waveform = b.NewValue.Waveform;
track = b.NewValue.Track;
+ // todo: i don't think this is safe, the track may not be loaded yet.
if (track.Length > 0)
{
MaxZoom = getZoomLevelForVisibleMilliseconds(500);
@@ -68,29 +91,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}, true);
}
- private float getZoomLevelForVisibleMilliseconds(double milliseconds) => (float)(track.Length / milliseconds);
-
- ///
- /// The timeline's scroll position in the last frame.
- ///
- private float lastScrollPosition;
-
- ///
- /// The track time in the last frame.
- ///
- private double lastTrackTime;
-
- ///
- /// Whether the user is currently dragging the timeline.
- ///
- private bool handlingDragInput;
-
- ///
- /// Whether the track was playing before a user drag event.
- ///
- private bool trackWasPlaying;
-
- private Track track;
+ private float getZoomLevelForVisibleMilliseconds(double milliseconds) => Math.Max(1, (float)(track.Length / milliseconds));
protected override void Update()
{
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index d92f3922c3..ac1f61c4fd 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -28,6 +28,7 @@ using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Cursor;
using osu.Game.Input.Bindings;
+using osu.Game.Online.API;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Setup;
@@ -72,6 +73,9 @@ namespace osu.Game.Screens.Edit
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
[BackgroundDependencyLoader]
private void load(OsuColour colours, GameHost host)
{
@@ -89,6 +93,14 @@ 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;
+ Beatmap.Value = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
+ }
+
try
{
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
@@ -101,9 +113,8 @@ namespace osu.Game.Screens.Edit
return;
}
- AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap));
+ AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin));
dependencies.CacheAs(editorBeatmap);
-
changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs(changeHandler);
@@ -148,6 +159,7 @@ namespace osu.Game.Screens.Edit
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
+ Mode = { Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose },
Items = new[]
{
new MenuItem("File")
@@ -284,7 +296,7 @@ namespace osu.Game.Screens.Edit
// this is a special case to handle the "pivot" scenario.
// if we are precise scrolling in one direction then change our mind and scroll backwards,
// the existing accumulation should be applied in the inverse direction to maintain responsiveness.
- if (Math.Sign(scrollAccumulation) != scrollDirection)
+ if (scrollAccumulation != 0 && Math.Sign(scrollAccumulation) != scrollDirection)
scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation));
scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1);
@@ -386,7 +398,11 @@ namespace osu.Game.Screens.Edit
break;
}
- LoadComponentAsync(currentScreen, screenContainer.Add);
+ LoadComponentAsync(currentScreen, newScreen =>
+ {
+ if (newScreen == currentScreen)
+ screenContainer.Add(newScreen);
+ });
}
private void seek(UIEvent e, int direction)
@@ -399,7 +415,14 @@ namespace osu.Game.Screens.Edit
clock.SeekForward(!clock.IsRunning, amount);
}
- private void saveBeatmap() => beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap);
+ private void saveBeatmap()
+ {
+ // apply any set-level metadata changes.
+ beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet);
+
+ // save the loaded beatmap's data stream.
+ beatmapManager.Save(playableBeatmap.BeatmapInfo, editorBeatmap, editorBeatmap.BeatmapSkin);
+ }
private void exportBeatmap()
{
diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs
index 23c8c9f605..061009e519 100644
--- a/osu.Game/Screens/Edit/EditorBeatmap.cs
+++ b/osu.Game/Screens/Edit/EditorBeatmap.cs
@@ -15,6 +15,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
+using osu.Game.Skinning;
namespace osu.Game.Screens.Edit
{
@@ -47,6 +48,8 @@ namespace osu.Game.Screens.Edit
public readonly IBeatmap PlayableBeatmap;
+ public readonly ISkin BeatmapSkin;
+
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
@@ -54,9 +57,10 @@ namespace osu.Game.Screens.Edit
private readonly Dictionary> startTimeBindables = new Dictionary>();
- public EditorBeatmap(IBeatmap playableBeatmap)
+ public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null)
{
PlayableBeatmap = playableBeatmap;
+ BeatmapSkin = beatmapSkin;
beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap);
diff --git a/osu.Game/Screens/Edit/EditorChangeHandler.cs b/osu.Game/Screens/Edit/EditorChangeHandler.cs
index 1553c2d2ef..927c823c64 100644
--- a/osu.Game/Screens/Edit/EditorChangeHandler.cs
+++ b/osu.Game/Screens/Edit/EditorChangeHandler.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Screens.Edit
using (var stream = new MemoryStream())
{
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
- new LegacyBeatmapEncoder(editorBeatmap).Encode(sw);
+ new LegacyBeatmapEncoder(editorBeatmap, editorBeatmap.BeatmapSkin).Encode(sw);
savedStates.Add(stream.ToArray());
}
diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs
index d42447ac4b..8b5f0aaa71 100644
--- a/osu.Game/Screens/Edit/EditorScreen.cs
+++ b/osu.Game/Screens/Edit/EditorScreen.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit
public void Exit()
{
- this.FadeOut(250).Expire();
+ Expire();
}
}
}
diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
index fc3dd4c105..57b7ce6940 100644
--- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
+++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
@@ -107,7 +107,7 @@ namespace osu.Game.Screens.Edit
protected override Texture GetBackground() => throw new NotImplementedException();
- protected override Track GetTrack() => throw new NotImplementedException();
+ protected override Track GetBeatmapTrack() => throw new NotImplementedException();
}
}
}
diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs
index 758dbc6e16..a2c8f19016 100644
--- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs
+++ b/osu.Game/Screens/Edit/Setup/SetupScreen.cs
@@ -1,13 +1,123 @@
// 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.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Beatmaps.Drawables;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterfaceV2;
+using osuTK;
+
namespace osu.Game.Screens.Edit.Setup
{
public class SetupScreen : EditorScreen
{
- public SetupScreen()
+ private FillFlowContainer flow;
+ private LabelledTextBox artistTextBox;
+ private LabelledTextBox titleTextBox;
+ private LabelledTextBox creatorTextBox;
+ private LabelledTextBox difficultyTextBox;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
{
- Child = new ScreenWhiteBox.UnderConstructionMessage("Setup mode");
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(50),
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = 10,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = colours.GreySeafoamDark,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Padding = new MarginPadding(10),
+ Child = flow = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Spacing = new Vector2(20),
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 250,
+ Masking = true,
+ CornerRadius = 10,
+ Child = new BeatmapBackgroundSprite(Beatmap.Value)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fill,
+ },
+ },
+ new OsuSpriteText
+ {
+ Text = "Beatmap metadata"
+ },
+ artistTextBox = new LabelledTextBox
+ {
+ Label = "Artist",
+ Current = { Value = Beatmap.Value.Metadata.Artist },
+ TabbableContentContainer = this
+ },
+ titleTextBox = new LabelledTextBox
+ {
+ Label = "Title",
+ Current = { Value = Beatmap.Value.Metadata.Title },
+ TabbableContentContainer = this
+ },
+ creatorTextBox = new LabelledTextBox
+ {
+ Label = "Creator",
+ Current = { Value = Beatmap.Value.Metadata.AuthorString },
+ TabbableContentContainer = this
+ },
+ difficultyTextBox = new LabelledTextBox
+ {
+ Label = "Difficulty Name",
+ Current = { Value = Beatmap.Value.BeatmapInfo.Version },
+ TabbableContentContainer = this
+ },
+ }
+ },
+ },
+ }
+ }
+ };
+
+ foreach (var item in flow.OfType())
+ item.OnCommit += onCommit;
+ }
+
+ private void onCommit(TextBox sender, bool newText)
+ {
+ if (!newText) return;
+
+ // for now, update these on commit rather than making BeatmapMetadata bindables.
+ // after switching database engines we can reconsider if switching to bindables is a good direction.
+ Beatmap.Value.Metadata.Artist = artistTextBox.Current.Value;
+ Beatmap.Value.Metadata.Title = titleTextBox.Current.Value;
+ Beatmap.Value.Metadata.AuthorString = creatorTextBox.Current.Value;
+ Beatmap.Value.BeatmapInfo.Version = difficultyTextBox.Current.Value;
}
}
}
diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
index 5c59cfbfe8..c0c0bcead2 100644
--- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
+++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs
@@ -112,8 +112,7 @@ namespace osu.Game.Screens.Edit.Timing
};
controlPoints = group.ControlPoints.GetBoundCopy();
- controlPoints.ItemsAdded += _ => createChildren();
- controlPoints.ItemsRemoved += _ => createChildren();
+ controlPoints.CollectionChanged += (_, __) => createChildren();
createChildren();
}
diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs
index a08a660e7e..8c40c8e721 100644
--- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs
+++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs
@@ -124,8 +124,7 @@ namespace osu.Game.Screens.Edit.Timing
selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true);
controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy();
- controlGroups.ItemsAdded += _ => createContent();
- controlGroups.ItemsRemoved += _ => createContent();
+ controlGroups.CollectionChanged += (sender, args) => createContent();
createContent();
}
diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs
index 906644ce14..879363ba08 100644
--- a/osu.Game/Screens/Edit/Timing/TimingSection.cs
+++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs
@@ -1,30 +1,30 @@
// 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.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing;
+using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Overlays.Settings;
namespace osu.Game.Screens.Edit.Timing
{
internal class TimingSection : Section
{
- private SettingsSlider bpm;
+ private SettingsSlider bpmSlider;
private SettingsEnumDropdown timeSignature;
+ private BPMTextBox bpmTextEntry;
[BackgroundDependencyLoader]
private void load()
{
Flow.AddRange(new Drawable[]
{
- bpm = new BPMSlider
- {
- Bindable = new TimingControlPoint().BeatLengthBindable,
- LabelText = "BPM",
- },
+ bpmTextEntry = new BPMTextBox(),
+ bpmSlider = new BPMSlider(),
timeSignature = new SettingsEnumDropdown
{
LabelText = "Time Signature"
@@ -36,7 +36,8 @@ namespace osu.Game.Screens.Edit.Timing
{
if (point.NewValue != null)
{
- bpm.Bindable = point.NewValue.BeatLengthBindable;
+ bpmSlider.Bindable = point.NewValue.BeatLengthBindable;
+ bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable;
timeSignature.Bindable = point.NewValue.TimeSignatureBindable;
}
}
@@ -52,34 +53,88 @@ namespace osu.Game.Screens.Edit.Timing
};
}
+ private class BPMTextBox : LabelledTextBox
+ {
+ private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
+
+ public BPMTextBox()
+ {
+ Label = "BPM";
+
+ OnCommit += (val, isNew) =>
+ {
+ if (!isNew) return;
+
+ if (double.TryParse(Current.Value, out double doubleVal))
+ {
+ try
+ {
+ beatLengthBindable.Value = beatLengthToBpm(doubleVal);
+ }
+ catch
+ {
+ // will restore the previous text value on failure.
+ beatLengthBindable.TriggerChange();
+ }
+ }
+ };
+
+ beatLengthBindable.BindValueChanged(val =>
+ {
+ Current.Value = beatLengthToBpm(val.NewValue).ToString("N2");
+ }, true);
+ }
+
+ public Bindable Bindable
+ {
+ get => beatLengthBindable;
+ set
+ {
+ // incoming will be beat length, not bpm
+ beatLengthBindable.UnbindBindings();
+ beatLengthBindable.BindTo(value);
+ }
+ }
+ }
+
private class BPMSlider : SettingsSlider
{
- private readonly BindableDouble beatLengthBindable = new BindableDouble();
+ private const double sane_minimum = 60;
+ private const double sane_maximum = 240;
- private BindableDouble bpmBindable;
+ private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
+ private readonly BindableDouble bpmBindable = new BindableDouble();
+
+ public BPMSlider()
+ {
+ beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true);
+ bpmBindable.BindValueChanged(bpm => bpmBindable.Default = beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
+
+ base.Bindable = bpmBindable;
+ }
public override Bindable Bindable
{
get => base.Bindable;
set
{
- // incoming will be beatlength
-
+ // incoming will be beat length, not bpm
beatLengthBindable.UnbindBindings();
beatLengthBindable.BindTo(value);
-
- base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value))
- {
- MinValue = beatLengthToBpm(beatLengthBindable.MaxValue),
- MaxValue = beatLengthToBpm(beatLengthBindable.MinValue),
- Default = beatLengthToBpm(beatLengthBindable.Default),
- };
-
- bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue));
}
}
- private double beatLengthToBpm(double beatLength) => 60000 / beatLength;
+ private void updateCurrent(double newValue)
+ {
+ // we use a more sane range for the slider display unless overridden by the user.
+ // if a value comes in outside our range, we should expand temporarily.
+ bpmBindable.MinValue = Math.Min(newValue, sane_minimum);
+ bpmBindable.MaxValue = Math.Max(newValue, sane_maximum);
+
+ bpmBindable.Value = newValue;
+ }
}
+
+ private static double beatLengthToBpm(double beatLength) => 60000 / beatLength;
}
}
diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs
index 761f842c22..e19037c2c4 100644
--- a/osu.Game/Screens/IOsuScreen.cs
+++ b/osu.Game/Screens/IOsuScreen.cs
@@ -39,9 +39,9 @@ namespace osu.Game.Screens
bool HideOverlaysOnEnter { get; }
///
- /// Whether overlays should be able to be opened once this screen is entered or resumed.
+ /// Whether overlays should be able to be opened when this screen is current.
///
- OverlayActivation InitialOverlayActivationMode { get; }
+ IBindable OverlayActivationMode { get; }
///
/// The amount of parallax to be applied while this screen is displayed.
diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs
index 5ba7a8ddc3..4becdd58cd 100644
--- a/osu.Game/Screens/Menu/ButtonSystem.cs
+++ b/osu.Game/Screens/Menu/ButtonSystem.cs
@@ -270,9 +270,6 @@ namespace osu.Game.Screens.Menu
ButtonSystemState lastState = state;
state = value;
- if (game != null)
- game.OverlayActivationMode.Value = state == ButtonSystemState.Exit ? OverlayActivation.Disabled : OverlayActivation.All;
-
updateLogoState(lastState);
Logger.Log($"{nameof(ButtonSystem)}'s state changed from {lastState} to {state}");
diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs
index 986de1edf0..fcb9aacd76 100644
--- a/osu.Game/Screens/Menu/Disclaimer.cs
+++ b/osu.Game/Screens/Menu/Disclaimer.cs
@@ -190,7 +190,7 @@ namespace osu.Game.Screens.Menu
{
"You can press Ctrl-T anywhere in the game to toggle the toolbar!",
"You can press Ctrl-O anywhere in the game to access options!",
- "All settings are dynamic and take effect in real-time. Try changing the skin while playing!",
+ "All settings are dynamic and take effect in real-time. Try pausing and changing the skin while playing!",
"New features are coming online every update. Make sure to stay up-to-date!",
"If you find the UI too large or small, try adjusting UI scale in settings!",
"Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!",
diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs
index 5f91aaad15..1df5c503d6 100644
--- a/osu.Game/Screens/Menu/IntroScreen.cs
+++ b/osu.Game/Screens/Menu/IntroScreen.cs
@@ -12,6 +12,7 @@ using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.IO.Archives;
+using osu.Game.Overlays;
using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning;
using osuTK;
@@ -43,9 +44,7 @@ namespace osu.Game.Screens.Menu
private WorkingBeatmap initialBeatmap;
- protected Track Track => initialBeatmap?.Track;
-
- private readonly BindableDouble exitingVolumeFade = new BindableDouble(1);
+ protected ITrack Track { get; private set; }
private const int exit_delay = 3000;
@@ -60,8 +59,12 @@ namespace osu.Game.Screens.Menu
[Resolved]
private AudioManager audio { get; set; }
+ [Resolved]
+ private MusicController musicController { get; set; }
+
///
/// Whether the is provided by osu! resources, rather than a user beatmap.
+ /// Only valid during or after .
///
protected bool UsingThemedIntro { get; private set; }
@@ -111,7 +114,6 @@ namespace osu.Game.Screens.Menu
if (setInfo != null)
{
initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
- UsingThemedIntro = !(Track is TrackVirtual);
}
return UsingThemedIntro;
@@ -123,17 +125,35 @@ namespace osu.Game.Screens.Menu
this.FadeIn(300);
double fadeOutTime = exit_delay;
+
+ var track = musicController.CurrentTrack;
+
+ // ensure the track doesn't change or loop as we are exiting.
+ track.Looping = false;
+ Beatmap.Disabled = true;
+
// we also handle the exit transition.
if (MenuVoice.Value)
+ {
seeya.Play();
+
+ // if playing the outro voice, we have more time to have fun with the background track.
+ // initially fade to almost silent then ramp out over the remaining time.
+ const double initial_fade = 200;
+ track
+ .VolumeTo(0.03f, initial_fade).Then()
+ .VolumeTo(0, fadeOutTime - initial_fade, Easing.In);
+ }
else
+ {
fadeOutTime = 500;
- audio.AddAdjustment(AdjustableProperty.Volume, exitingVolumeFade);
- this.TransformBindableTo(exitingVolumeFade, 0, fadeOutTime).OnComplete(_ => this.Exit());
+ // if outro voice is turned off, just do a simple fade out.
+ track.VolumeTo(0, fadeOutTime, Easing.Out);
+ }
//don't want to fade out completely else we will stop running updates.
- Game.FadeTo(0.01f, fadeOutTime);
+ Game.FadeTo(0.01f, fadeOutTime).OnComplete(_ => this.Exit());
base.OnResuming(last);
}
@@ -164,6 +184,11 @@ namespace osu.Game.Screens.Menu
if (!resuming)
{
beatmap.Value = initialBeatmap;
+ Track = initialBeatmap.Track;
+ UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice;
+
+ // ensure the track starts at maximum volume
+ musicController.CurrentTrack.FinishTransforms();
logo.MoveTo(new Vector2(0.5f));
logo.ScaleTo(Vector2.One);
diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs
index 6731fef6f7..d92d38da45 100644
--- a/osu.Game/Screens/Menu/IntroSequence.cs
+++ b/osu.Game/Screens/Menu/IntroSequence.cs
@@ -205,6 +205,7 @@ namespace osu.Game.Screens.Menu
const int line_end_offset = 120;
smallRing.Foreground.ResizeTo(1, line_duration, Easing.OutQuint);
+ smallRing.Delay(400).FadeColour(Color4.Black, 300);
lineTopLeft.MoveTo(new Vector2(-line_end_offset, -line_end_offset), line_duration, Easing.OutQuint);
lineTopRight.MoveTo(new Vector2(line_end_offset, -line_end_offset), line_duration, Easing.OutQuint);
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index a9ef20436f..a96fddb5ad 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -44,7 +44,7 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load()
{
- if (MenuVoice.Value && !UsingThemedIntro)
+ if (MenuVoice.Value)
welcome = audio.Samples.Get(@"Intro/welcome");
}
@@ -64,7 +64,8 @@ namespace osu.Game.Screens.Menu
}, t =>
{
AddInternal(t);
- welcome?.Play();
+ if (!UsingThemedIntro)
+ welcome?.Play();
StartTrack();
});
diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs
index bf42e36e8c..e81646456f 100644
--- a/osu.Game/Screens/Menu/IntroWelcome.cs
+++ b/osu.Game/Screens/Menu/IntroWelcome.cs
@@ -39,8 +39,6 @@ namespace osu.Game.Screens.Menu
welcome = audio.Samples.Get(@"Intro/Welcome/welcome");
pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano");
-
- Track.Looping = true;
}
protected override void LogoArriving(OsuLogo logo, bool resuming)
@@ -49,6 +47,8 @@ namespace osu.Game.Screens.Menu
if (!resuming)
{
+ Track.Looping = true;
+
LoadComponentAsync(new WelcomeIntroSequence
{
RelativeSizeAxes = Axes.Both
diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs
index 57252d557e..c3ecd75963 100644
--- a/osu.Game/Screens/Menu/MainMenu.cs
+++ b/osu.Game/Screens/Menu/MainMenu.cs
@@ -5,7 +5,6 @@ using System.Linq;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Allocation;
-using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Platform;
@@ -46,8 +45,8 @@ namespace osu.Game.Screens.Menu
[Resolved]
private GameHost host { get; set; }
- [Resolved(canBeNull: true)]
- private MusicController music { get; set; }
+ [Resolved]
+ private MusicController musicController { get; set; }
[Resolved(canBeNull: true)]
private LoginOverlay login { get; set; }
@@ -62,8 +61,6 @@ namespace osu.Game.Screens.Menu
protected override BackgroundScreen CreateBackground() => background;
- internal Track Track { get; private set; }
-
private Bindable holdDelay;
private Bindable loginDisplayed;
@@ -101,7 +98,11 @@ namespace osu.Game.Screens.Menu
{
buttons = new ButtonSystem
{
- OnEdit = delegate { this.Push(new Editor()); },
+ OnEdit = delegate
+ {
+ Beatmap.SetDefault();
+ this.Push(new Editor());
+ },
OnSolo = onSolo,
OnMulti = delegate { this.Push(new Multiplayer()); },
OnExit = confirmAndExit,
@@ -177,15 +178,14 @@ namespace osu.Game.Screens.Menu
base.OnEntering(last);
buttons.FadeInFromZero(500);
- Track = Beatmap.Value.Track;
var metadata = Beatmap.Value.Metadata;
- if (last is IntroScreen && Track != null)
+ if (last is IntroScreen && musicController.TrackLoaded)
{
- if (!Track.IsRunning)
+ if (!musicController.CurrentTrack.IsRunning)
{
- Track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * Track.Length);
- Track.Start();
+ musicController.CurrentTrack.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * musicController.CurrentTrack.Length);
+ musicController.CurrentTrack.Start();
}
}
@@ -260,7 +260,7 @@ namespace osu.Game.Screens.Menu
// we may have consumed our preloaded instance, so let's make another.
preloadSongSelect();
- music.EnsurePlayingSomething();
+ musicController.EnsurePlayingSomething();
}
public override bool OnExiting(IScreen next)
@@ -280,6 +280,7 @@ namespace osu.Game.Screens.Menu
}
buttons.State = ButtonSystemState.Exit;
+ OverlayActivationMode.Value = OverlayActivation.Disabled;
songTicker.Hide();
diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs
index f5e4b078da..4515ee8ed0 100644
--- a/osu.Game/Screens/Menu/OsuLogo.cs
+++ b/osu.Game/Screens/Menu/OsuLogo.cs
@@ -17,6 +17,7 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
+using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@@ -46,7 +47,6 @@ namespace osu.Game.Screens.Menu
private SampleChannel sampleBeat;
private readonly Container colourAndTriangles;
-
private readonly Triangles triangles;
///
@@ -319,6 +319,9 @@ namespace osu.Game.Screens.Menu
intro.Delay(length + fade).FadeOut();
}
+ [Resolved]
+ private MusicController musicController { get; set; }
+
protected override void Update()
{
base.Update();
@@ -327,9 +330,9 @@ namespace osu.Game.Screens.Menu
const float velocity_adjust_cutoff = 0.98f;
const float paused_velocity = 0.5f;
- if (Beatmap.Value.Track.IsRunning)
+ if (musicController.CurrentTrack.IsRunning)
{
- var maxAmplitude = lastBeatIndex >= 0 ? Beatmap.Value.Track.CurrentAmplitudes.Maximum : 0;
+ var maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0;
logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed));
if (maxAmplitude > velocity_adjust_cutoff)
diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs
index 653cb3791a..cd8695286b 100644
--- a/osu.Game/Screens/Multi/Header.cs
+++ b/osu.Game/Screens/Multi/Header.cs
@@ -11,8 +11,8 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
-using osu.Game.Overlays.SearchableList;
using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
@@ -42,7 +42,7 @@ namespace osu.Game.Screens.Multi
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
+ Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
title = new MultiHeaderTitle
diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
index 447c99039a..321d7b0a19 100644
--- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using System.Collections.Specialized;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -53,8 +54,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components
protected override void LoadComplete()
{
- rooms.ItemsAdded += addRooms;
- rooms.ItemsRemoved += removeRooms;
+ rooms.CollectionChanged += roomsChanged;
roomManager.RoomsUpdated += updateSorting;
rooms.BindTo(roomManager.Rooms);
@@ -82,6 +82,20 @@ namespace osu.Game.Screens.Multi.Lounge.Components
});
}
+ private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args)
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ addRooms(args.NewItems.Cast());
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ removeRooms(args.OldItems.Cast());
+ break;
+ }
+ }
+
private void addRooms(IEnumerable rooms)
{
foreach (var room in rooms)
diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
index ff7d56a95b..a1e99c83b2 100644
--- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs
@@ -12,7 +12,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays;
-using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Screens.Multi.Match;
@@ -32,7 +31,7 @@ namespace osu.Game.Screens.Multi.Lounge
[Resolved]
private Bindable selectedRoom { get; set; }
- [Resolved(canBeNull: true)]
+ [Resolved]
private MusicController music { get; set; }
private bool joiningRoom;
@@ -102,8 +101,8 @@ namespace osu.Game.Screens.Multi.Lounge
content.Padding = new MarginPadding
{
Top = Filter.DrawHeight,
- Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING,
- Right = SearchableListOverlay.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING,
+ Left = WaveOverlayContainer.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING,
+ Right = WaveOverlayContainer.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING,
};
}
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
index 1afbf5c32a..f2409d64e7 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs
@@ -14,8 +14,6 @@ namespace osu.Game.Screens.Multi.Match.Components
{
public class MatchLeaderboard : Leaderboard
{
- public Action> ScoresLoaded;
-
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable roomId { get; set; }
@@ -39,18 +37,20 @@ namespace osu.Game.Screens.Multi.Match.Components
if (roomId.Value == null)
return null;
- var req = new GetRoomScoresRequest(roomId.Value ?? 0);
+ var req = new GetRoomLeaderboardRequest(roomId.Value ?? 0);
req.Success += r =>
{
- scoresCallback?.Invoke(r);
- ScoresLoaded?.Invoke(r);
+ scoresCallback?.Invoke(r.Leaderboard);
+ TopScore = r.UserScore;
};
return req;
}
protected override LeaderboardScore CreateDrawableScore(APIUserScoreAggregate model, int index) => new MatchLeaderboardScore(model, index);
+
+ protected override LeaderboardScore CreateDrawableTopScore(APIUserScoreAggregate model) => new MatchLeaderboardScore(model, model.Position, false);
}
public enum MatchLeaderboardScope
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs
index 73a40d9579..1fabdbb86a 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs
@@ -14,8 +14,8 @@ namespace osu.Game.Screens.Multi.Match.Components
{
private readonly APIUserScoreAggregate score;
- public MatchLeaderboardScore(APIUserScoreAggregate score, int rank)
- : base(score.CreateScoreInfo(), rank)
+ public MatchLeaderboardScore(APIUserScoreAggregate score, int? rank, bool allowHighlight = true)
+ : base(score.CreateScoreInfo(), rank, allowHighlight)
{
this.score = score;
}
diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
index 49a0fc434b..caefc194b1 100644
--- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
+++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs
@@ -14,7 +14,7 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
-using osu.Game.Overlays.SearchableList;
+using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
@@ -117,7 +117,7 @@ namespace osu.Game.Screens.Multi.Match.Components
{
new Container
{
- Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
+ Padding = new MarginPadding { Horizontal = WaveOverlayContainer.WIDTH_PADDING },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
index 7c2d5cf85d..0d2adeb27c 100644
--- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
+++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs
@@ -172,7 +172,7 @@ namespace osu.Game.Screens.Multi.Match
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
- new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 240),
+ new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 120),
}
},
null
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 872a1cd39a..a44d14fb5c 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -44,9 +44,13 @@ namespace osu.Game.Screens
public virtual bool HideOverlaysOnEnter => false;
///
- /// Whether overlays should be able to be opened once this screen is entered or resumed.
+ /// The initial overlay activation mode to use when this screen is entered for the first time.
///
- public virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All;
+ protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All;
+
+ protected readonly Bindable OverlayActivationMode;
+
+ IBindable IOsuScreen.OverlayActivationMode => OverlayActivationMode;
public virtual bool CursorVisible => true;
@@ -138,6 +142,8 @@ namespace osu.Game.Screens
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
+
+ OverlayActivationMode = new Bindable(InitialOverlayActivationMode);
}
[BackgroundDependencyLoader(true)]
diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs
index 0653373c91..7a9cb3dddd 100644
--- a/osu.Game/Screens/Play/GameplayClockContainer.cs
+++ b/osu.Game/Screens/Play/GameplayClockContainer.cs
@@ -2,7 +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;
using osu.Framework;
@@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
-using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens.Play
{
@@ -25,12 +24,9 @@ namespace osu.Game.Screens.Play
public class GameplayClockContainer : Container
{
private readonly WorkingBeatmap beatmap;
- private readonly IReadOnlyList mods;
- ///
- /// The 's track.
- ///
- private Track track;
+ [NotNull]
+ private ITrack track;
public readonly BindableBool IsPaused = new BindableBool();
@@ -63,17 +59,16 @@ namespace osu.Game.Screens.Play
private readonly FramedOffsetClock platformOffsetClock;
- public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime)
+ public GameplayClockContainer(WorkingBeatmap beatmap, double gameplayStartTime)
{
this.beatmap = beatmap;
- this.mods = mods;
this.gameplayStartTime = gameplayStartTime;
+ track = beatmap.Track;
+
firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime;
RelativeSizeAxes = Axes.Both;
- track = beatmap.Track;
-
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
// Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited.
@@ -123,13 +118,10 @@ namespace osu.Game.Screens.Play
public void Restart()
{
- // The Reset() call below causes speed adjustments to be reset in an async context, leading to deadlocks.
- // The deadlock can be prevented by resetting the track synchronously before entering the async context.
- track.ResetSpeedAdjustments();
-
Task.Run(() =>
{
- track.Reset();
+ track.Seek(0);
+ track.Stop();
Schedule(() =>
{
@@ -195,16 +187,13 @@ namespace osu.Game.Screens.Play
}
///
- /// Changes the backing clock to avoid using the originally provided beatmap's track.
+ /// Changes the backing clock to avoid using the originally provided track.
///
public void StopUsingBeatmapClock()
{
- if (track != beatmap.Track)
- return;
-
removeSourceClockAdjustments();
- track = new TrackVirtual(beatmap.Track.Length);
+ track = new TrackVirtual(track.Length);
adjustableClock.ChangeSource(track);
}
@@ -220,33 +209,29 @@ namespace osu.Game.Screens.Play
private void updateRate()
{
- if (track == null) return;
-
- speedAdjustmentsApplied = true;
- track.ResetSpeedAdjustments();
+ if (speedAdjustmentsApplied)
+ return;
track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
track.AddAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
- foreach (var mod in mods.OfType())
- mod.ApplyToTrack(track);
+ speedAdjustmentsApplied = true;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
-
removeSourceClockAdjustments();
- track = null;
}
private void removeSourceClockAdjustments()
{
- if (speedAdjustmentsApplied)
- {
- track.ResetSpeedAdjustments();
- speedAdjustmentsApplied = false;
- }
+ if (!speedAdjustmentsApplied) return;
+
+ track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
+ track.RemoveAdjustment(AdjustableProperty.Tempo, UserPlaybackRate);
+
+ speedAdjustmentsApplied = false;
}
private class HardwareCorrectionOffsetClock : FramedOffsetClock
diff --git a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs
deleted file mode 100644
index 7ae8bc0ddf..0000000000
--- a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-// 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.Graphics;
-using osu.Game.Graphics.UserInterface;
-
-namespace osu.Game.Screens.Play.HUD
-{
- ///
- /// Used to display combo with a roll-up animation in results screen.
- ///
- public class ComboResultCounter : RollingCounter
- {
- protected override double RollingDuration => 500;
- protected override Easing RollingEasing => Easing.Out;
-
- protected override double GetProportionalDuration(long currentValue, long newValue)
- {
- return currentValue > newValue ? currentValue - newValue : newValue - currentValue;
- }
-
- protected override string FormatCount(long count)
- {
- return $@"{count}x";
- }
-
- public override void Increment(long amount)
- {
- Current.Value += amount;
- }
- }
-}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 541275cf55..539f9227a3 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -50,7 +50,10 @@ namespace osu.Game.Screens.Play
public override bool HideOverlaysOnEnter => true;
- public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
+ protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
+
+ // We are managing our own adjustments (see OnEntering/OnExiting).
+ public override bool AllowRateAdjustments => false;
///
/// Whether gameplay should pause when the game window focus is lost.
@@ -77,6 +80,9 @@ namespace osu.Game.Screens.Play
[Resolved]
private IAPIProvider api { get; set; }
+ [Resolved]
+ private MusicController musicController { get; set; }
+
private SampleChannel sampleRestart;
public BreakOverlay BreakOverlay;
@@ -178,7 +184,7 @@ namespace osu.Game.Screens.Play
if (!ScoreProcessor.Mode.Disabled)
config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode);
- InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime);
+ InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime);
AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap));
AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer));
@@ -197,6 +203,10 @@ namespace osu.Game.Screens.Play
skipOverlay.Hide();
}
+ DrawableRuleset.IsPaused.BindValueChanged(_ => updateOverlayActivationMode());
+ DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updateOverlayActivationMode());
+ breakTracker.IsBreakTime.BindValueChanged(_ => updateOverlayActivationMode());
+
DrawableRuleset.HasReplayLoaded.BindValueChanged(_ => updatePauseOnFocusLostState(), true);
// bind clock into components that require it
@@ -341,6 +351,16 @@ namespace osu.Game.Screens.Play
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
}
+ private void updateOverlayActivationMode()
+ {
+ bool canTriggerOverlays = DrawableRuleset.IsPaused.Value || breakTracker.IsBreakTime.Value;
+
+ if (DrawableRuleset.HasReplayLoaded.Value || canTriggerOverlays)
+ OverlayActivationMode.Value = OverlayActivation.UserTriggered;
+ else
+ OverlayActivationMode.Value = OverlayActivation.Disabled;
+ }
+
private void updatePauseOnFocusLostState() =>
HUDOverlay.HoldToQuit.PauseOnFocusLost = PauseOnFocusLost
&& !DrawableRuleset.HasReplayLoaded.Value
@@ -627,6 +647,15 @@ namespace osu.Game.Screens.Play
foreach (var mod in Mods.Value.OfType())
mod.ApplyToHUD(HUDOverlay);
+
+ // Our mods are local copies of the global mods so they need to be re-applied to the track.
+ // This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack.
+ // Todo: In the future, player will receive in a track and will probably not have to worry about this...
+ musicController.ResetTrackAdjustments();
+ foreach (var mod in Mods.Value.OfType())
+ mod.ApplyToTrack(musicController.CurrentTrack);
+
+ updateOverlayActivationMode();
}
public override void OnSuspending(IScreen next)
@@ -660,6 +689,8 @@ namespace osu.Game.Screens.Play
// as we are no longer the current screen, we cannot guarantee the track is still usable.
GameplayClockContainer?.StopUsingBeatmapClock();
+ musicController.ResetTrackAdjustments();
+
fadeOut();
return base.OnExiting(next);
}
diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs
index 6933456e7e..288a107874 100644
--- a/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Statistics/AccuracyStatistic.cs
@@ -46,9 +46,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
protected override string FormatCount(double count) => count.FormatAccuracy();
- public override void Increment(double amount)
- => Current.Value += amount;
-
protected override OsuSpriteText CreateSpriteText() => base.CreateSpriteText().With(s =>
{
s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
diff --git a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs
index 043a560d12..e820831809 100644
--- a/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Statistics/CounterStatistic.cs
@@ -49,9 +49,6 @@ namespace osu.Game.Screens.Ranking.Expanded.Statistics
s.Font = OsuFont.Torus.With(size: 20, fixedWidth: true);
s.Spacing = new Vector2(-2, 0);
});
-
- public override void Increment(int amount)
- => Current.Value += amount;
}
}
}
diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs
index 7f6fd1eabe..65082d3fae 100644
--- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs
+++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs
@@ -36,8 +36,5 @@ namespace osu.Game.Screens.Ranking.Expanded
s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true);
s.Spacing = new Vector2(-5, 0);
});
-
- public override void Increment(long amount)
- => Current.Value += amount;
}
}
diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
index d0142e57fe..b76842f405 100644
--- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
+++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
@@ -74,23 +74,33 @@ namespace osu.Game.Screens.Ranking
{
button.State.Value = state.NewValue;
- switch (replayAvailability)
- {
- case ReplayAvailability.Local:
- button.TooltipText = @"watch replay";
- break;
-
- case ReplayAvailability.Online:
- button.TooltipText = @"download replay";
- break;
-
- default:
- button.TooltipText = @"replay unavailable";
- break;
- }
+ updateTooltip();
}, true);
- button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable;
+ Model.BindValueChanged(_ =>
+ {
+ button.Enabled.Value = replayAvailability != ReplayAvailability.NotAvailable;
+
+ updateTooltip();
+ }, true);
+ }
+
+ private void updateTooltip()
+ {
+ switch (replayAvailability)
+ {
+ case ReplayAvailability.Local:
+ button.TooltipText = @"watch replay";
+ break;
+
+ case ReplayAvailability.Online:
+ button.TooltipText = @"download replay";
+ break;
+
+ default:
+ button.TooltipText = @"replay unavailable";
+ break;
+ }
}
private enum ReplayAvailability
diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs
index 527da429ed..45fdc3ff33 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)).ToList();
+ this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss).ToList();
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs
index e6c4ab1c4e..6fe7e4eda8 100644
--- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs
+++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticItem.cs
@@ -41,13 +41,14 @@ namespace osu.Game.Screens.Ranking.Statistics
{
Text = Name,
Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft
+ Origin = Anchor.CentreLeft,
+ Font = OsuFont.GetFont(size: 14)
},
value = new OsuSpriteText
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
- Font = OsuFont.Torus.With(weight: FontWeight.Bold)
+ Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold)
}
});
}
@@ -58,12 +59,19 @@ namespace osu.Game.Screens.Ranking.Statistics
///
public class SimpleStatisticItem : SimpleStatisticItem
{
+ private TValue value;
+
///
/// The statistic's value to be displayed.
///
public new TValue Value
{
- set => base.Value = DisplayValue(value);
+ get => value;
+ set
+ {
+ this.value = value;
+ base.Value = DisplayValue(value);
+ }
}
///
diff --git a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs
similarity index 93%
rename from osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs
rename to osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs
index 16501aae54..8b503cc04e 100644
--- a/osu.Game/Screens/Ranking/Statistics/SimpleStatisticRow.cs
+++ b/osu.Game/Screens/Ranking/Statistics/SimpleStatisticTable.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -13,10 +14,10 @@ using osu.Framework.Graphics.Shapes;
namespace osu.Game.Screens.Ranking.Statistics
{
///
- /// Represents a statistic row with simple statistics (ones that only need textual display).
+ /// Represents a table with simple statistics (ones that only need textual display).
/// Richer visualisations should be done with s and s.
///
- public class SimpleStatisticRow : CompositeDrawable
+ public class SimpleStatisticTable : CompositeDrawable
{
private readonly SimpleStatisticItem[] items;
private readonly int columnCount;
@@ -28,7 +29,7 @@ namespace osu.Game.Screens.Ranking.Statistics
///
/// The number of columns to layout the into.
/// The s to display in this row.
- public SimpleStatisticRow(int columnCount, IEnumerable items)
+ public SimpleStatisticTable(int columnCount, [ItemNotNull] IEnumerable items)
{
if (columnCount < 1)
throw new ArgumentOutOfRangeException(nameof(columnCount));
diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs
index ed98698411..485d24d024 100644
--- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs
+++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs
@@ -32,33 +32,9 @@ namespace osu.Game.Screens.Ranking.Statistics
AutoSizeAxes = Axes.Y,
Content = new[]
{
- new Drawable[]
+ new[]
{
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(5, 0),
- Children = new Drawable[]
- {
- new Circle
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Height = 9,
- Width = 4,
- Colour = Color4Extensions.FromHex("#00FFAA")
- },
- new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Text = item.Name,
- Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
- }
- }
- }
+ createHeader(item)
},
new Drawable[]
{
@@ -78,5 +54,37 @@ namespace osu.Game.Screens.Ranking.Statistics
}
};
}
+
+ private static Drawable createHeader(StatisticItem item)
+ {
+ if (string.IsNullOrEmpty(item.Name))
+ return Empty();
+
+ return new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(5, 0),
+ Children = new Drawable[]
+ {
+ new Circle
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Height = 9,
+ Width = 4,
+ Colour = Color4Extensions.FromHex("#00FFAA")
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Text = item.Name,
+ Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold),
+ }
+ }
+ };
+ }
}
}
diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs
index e959ed24fc..4903983759 100644
--- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs
+++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Screens.Ranking.Statistics
///
/// Creates a new , to be displayed inside a in the results screen.
///
- /// The name of the item.
+ /// The name of the item. Can be to hide the item header.
/// The content to be displayed.
/// The of this item. This can be thought of as the column dimension of an encompassing .
public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null)
diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs
index 7f406331cd..c2ace6a04e 100644
--- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs
+++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs
@@ -94,14 +94,15 @@ namespace osu.Game.Screens.Ranking.Statistics
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(30, 15),
+ Alpha = 0
};
foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap))
{
rows.Add(new GridContainer
{
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Content = new[]
@@ -125,6 +126,7 @@ namespace osu.Game.Screens.Ranking.Statistics
spinner.Hide();
content.Add(d);
+ d.FadeIn(250, Easing.OutQuint);
}, localCancellationSource.Token);
}), localCancellationSource.Token);
}
diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs
new file mode 100644
index 0000000000..18a2238784
--- /dev/null
+++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs
@@ -0,0 +1,40 @@
+// 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.Collections.Generic;
+using System.Linq;
+using osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Screens.Ranking.Statistics
+{
+ ///
+ /// Displays the unstable rate statistic for a given play.
+ ///
+ public class UnstableRate : SimpleStatisticItem
+ {
+ ///
+ /// Creates and computes an statistic.
+ ///
+ /// Sequence of s to calculate the unstable rate based on.
+ public UnstableRate(IEnumerable hitEvents)
+ : base("Unstable Rate")
+ {
+ var timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result != HitResult.Miss)
+ .Select(ev => ev.TimeOffset).ToArray();
+ Value = 10 * standardDeviation(timeOffsets);
+ }
+
+ private static double standardDeviation(double[] timeOffsets)
+ {
+ if (timeOffsets.Length == 0)
+ return double.NaN;
+
+ var mean = timeOffsets.Average();
+ var squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
+ return Math.Sqrt(squares / timeOffsets.Length);
+ }
+
+ protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2");
+ }
+}
diff --git a/osu.Game/Screens/ScorePresentType.cs b/osu.Game/Screens/ScorePresentType.cs
new file mode 100644
index 0000000000..3216f92091
--- /dev/null
+++ b/osu.Game/Screens/ScorePresentType.cs
@@ -0,0 +1,11 @@
+// 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
+{
+ public enum ScorePresentType
+ {
+ Results,
+ Gameplay
+ }
+}
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
index 27ce9e82dd..2a3eb8c67a 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs
@@ -27,6 +27,7 @@ using osu.Framework.Logging;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Ranking.Expanded;
namespace osu.Game.Screens.Select
{
@@ -222,10 +223,20 @@ namespace osu.Game.Screens.Select
Direction = FillDirection.Vertical,
Padding = new MarginPadding { Top = 14, Right = shear_width / 2 },
AutoSizeAxes = Axes.Both,
- Children = new Drawable[]
+ Shear = wedged_container_shear,
+ Children = new[]
{
+ createStarRatingDisplay(beatmapInfo).With(display =>
+ {
+ display.Anchor = Anchor.TopRight;
+ display.Origin = Anchor.TopRight;
+ display.Shear = -wedged_container_shear;
+ }),
StatusPill = new BeatmapSetOnlineStatusPill
{
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ Shear = -wedged_container_shear,
TextSize = 11,
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
Status = beatmapInfo.Status,
@@ -282,6 +293,13 @@ namespace osu.Game.Screens.Select
StatusPill.Hide();
}
+ private static Drawable createStarRatingDisplay(BeatmapInfo beatmapInfo) => beatmapInfo.StarDifficulty > 0
+ ? new StarRatingDisplay(beatmapInfo)
+ {
+ Margin = new MarginPadding { Bottom = 5 }
+ }
+ : Empty();
+
private void setMetadata(string source)
{
ArtistLabel.Text = artistBinding.Value;
@@ -300,14 +318,14 @@ namespace osu.Game.Screens.Select
labels.Add(new InfoLabel(new BeatmapStatistic
{
Name = "Length",
- Icon = FontAwesome.Regular.Clock,
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length),
Content = TimeSpan.FromMilliseconds(b.BeatmapInfo.Length).ToString(@"m\:ss"),
}));
labels.Add(new InfoLabel(new BeatmapStatistic
{
Name = "BPM",
- Icon = FontAwesome.Regular.Circle,
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm),
Content = getBPMRange(b),
}));
@@ -400,10 +418,18 @@ namespace osu.Game.Screens.Select
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
- Scale = new Vector2(0.8f),
Colour = Color4Extensions.FromHex(@"f7dd55"),
- Icon = statistic.Icon,
+ Icon = FontAwesome.Regular.Circle,
+ Size = new Vector2(0.8f)
},
+ statistic.CreateIcon().With(i =>
+ {
+ i.Anchor = Anchor.Centre;
+ i.Origin = Anchor.Centre;
+ i.RelativeSizeAxes = Axes.Both;
+ i.Colour = Color4Extensions.FromHex(@"f7dd55");
+ i.Size = new Vector2(0.64f);
+ }),
}
},
new OsuSpriteText
diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
index 8e85eb4eb2..8ddae67dba 100644
--- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
+++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs
@@ -9,7 +9,6 @@ using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
-using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@@ -41,25 +40,8 @@ namespace osu.Game.Screens.Select.Leaderboards
}
}
- public APILegacyUserTopScoreInfo TopScore
- {
- get => topScoreContainer.Score.Value;
- set
- {
- if (value == null)
- topScoreContainer.Hide();
- else
- {
- topScoreContainer.Show();
- topScoreContainer.Score.Value = value;
- }
- }
- }
-
private bool filterMods;
- private UserTopScoreContainer topScoreContainer;
-
private IBindable> itemRemoved;
///
@@ -101,11 +83,6 @@ namespace osu.Game.Screens.Select.Leaderboards
UpdateScores();
};
- Content.Add(topScoreContainer = new UserTopScoreContainer
- {
- ScoreSelected = s => ScoreSelected?.Invoke(s)
- });
-
itemRemoved = scoreManager.ItemRemoved.GetBoundCopy();
itemRemoved.BindValueChanged(onScoreRemoved);
}
@@ -183,7 +160,7 @@ namespace osu.Game.Screens.Select.Leaderboards
req.Success += r =>
{
scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets)));
- TopScore = r.UserScore;
+ TopScore = r.UserScore?.CreateScoreInfo(rulesets);
};
return req;
@@ -193,5 +170,10 @@ namespace osu.Game.Screens.Select.Leaderboards
{
Action = () => ScoreSelected?.Invoke(model)
};
+
+ protected override LeaderboardScore CreateDrawableTopScore(ScoreInfo model) => new LeaderboardScore(model, model.Position, false)
+ {
+ Action = () => ScoreSelected?.Invoke(model)
+ };
}
}
diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs
index 96a48fa3ac..8692833a21 100644
--- a/osu.Game/Screens/Select/MatchSongSelect.cs
+++ b/osu.Game/Screens/Select/MatchSongSelect.cs
@@ -76,9 +76,7 @@ namespace osu.Game.Screens.Select
item.Ruleset.Value = Ruleset.Value;
item.RequiredMods.Clear();
- item.RequiredMods.AddRange(Mods.Value);
-
- Mods.Value = Mods.Value.Select(m => m.CreateCopy()).ToArray();
+ item.RequiredMods.AddRange(Mods.Value.Select(m => m.CreateCopy()));
}
}
}
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 74a5ee8309..ddbb021054 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -4,7 +4,6 @@
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.Graphics;
using osu.Framework.Graphics.Containers;
@@ -32,6 +31,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics.UserInterface;
@@ -99,7 +99,7 @@ namespace osu.Game.Screens.Select
private readonly Bindable decoupledRuleset = new Bindable();
- [Resolved(canBeNull: true)]
+ [Resolved]
private MusicController music { get; set; }
[BackgroundDependencyLoader(true)]
@@ -561,15 +561,15 @@ namespace osu.Game.Screens.Select
BeatmapDetails.Refresh();
- Beatmap.Value.Track.Looping = true;
- music?.ResetTrackAdjustments();
+ music.CurrentTrack.Looping = true;
+ music.ResetTrackAdjustments();
if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending)
{
updateComponentFromBeatmap(Beatmap.Value);
// restart playback on returning to song select, regardless.
- music?.Play();
+ music.Play();
}
this.FadeIn(250);
@@ -586,8 +586,7 @@ namespace osu.Game.Screens.Select
BeatmapOptions.Hide();
- if (Beatmap.Value.Track != null)
- Beatmap.Value.Track.Looping = false;
+ music.CurrentTrack.Looping = false;
this.ScaleTo(1.1f, 250, Easing.InSine);
@@ -608,8 +607,7 @@ namespace osu.Game.Screens.Select
FilterControl.Deactivate();
- if (Beatmap.Value.Track != null)
- Beatmap.Value.Track.Looping = false;
+ music.CurrentTrack.Looping = false;
return false;
}
@@ -650,11 +648,10 @@ namespace osu.Game.Screens.Select
BeatmapDetails.Beatmap = beatmap;
- if (beatmap.Track != null)
- beatmap.Track.Looping = true;
+ music.CurrentTrack.Looping = true;
}
- private readonly WeakReference lastTrack = new WeakReference(null);
+ private readonly WeakReference lastTrack = new WeakReference(null);
///
/// Ensures some music is playing for the current track.
@@ -662,14 +659,14 @@ namespace osu.Game.Screens.Select
///
private void ensurePlayingSelected()
{
- Track track = Beatmap.Value.Track;
+ ITrack track = music.CurrentTrack;
bool isNewTrack = !lastTrack.TryGetTarget(out var last) || last != track;
track.RestartPoint = Beatmap.Value.Metadata.PreviewTime;
- if (!track.IsRunning && (music?.IsUserPaused != true || isNewTrack))
- music?.Play(true);
+ if (!track.IsRunning && (music.IsUserPaused != true || isNewTrack))
+ music.Play(true);
lastTrack.SetTarget(track);
}
diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs
index c3e36c8e9d..e5e134fd39 100644
--- a/osu.Game/Screens/StartupScreen.cs
+++ b/osu.Game/Screens/StartupScreen.cs
@@ -18,6 +18,6 @@ namespace osu.Game.Screens
public override bool AllowRateAdjustments => false;
- public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled;
+ protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled;
}
}
diff --git a/osu.Game/Skinning/LegacyColourCompatibility.cs b/osu.Game/Skinning/LegacyColourCompatibility.cs
new file mode 100644
index 0000000000..b842b50426
--- /dev/null
+++ b/osu.Game/Skinning/LegacyColourCompatibility.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 osu.Framework.Graphics;
+using osuTK.Graphics;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// Compatibility methods to convert osu!stable colours to osu!lazer-compatible ones. Should be used for legacy skins only.
+ ///
+ public static class LegacyColourCompatibility
+ {
+ ///
+ /// Forces an alpha of 1 if a given is fully transparent.
+ ///
+ ///
+ /// This is equivalent to setting colour post-constructor in osu!stable.
+ ///
+ /// The to disallow zero alpha on.
+ /// The resultant .
+ public static Color4 DisallowZeroAlpha(Color4 colour)
+ {
+ if (colour.A == 0)
+ colour.A = 1;
+ return colour;
+ }
+
+ ///
+ /// Applies a to a , doubling the alpha value into the property.
+ ///
+ ///
+ /// This is equivalent to setting colour in the constructor in osu!stable.
+ ///
+ /// The to apply the colour to.
+ /// The to apply.
+ /// The given .
+ public static T ApplyWithDoubledAlpha(T drawable, Color4 colour)
+ where T : Drawable
+ {
+ drawable.Alpha = colour.A;
+ drawable.Colour = DisallowZeroAlpha(colour);
+ return drawable;
+ }
+ }
+}
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
index af7d6007f3..35a6140cbc 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Skinning
public readonly int Keys;
- public Dictionary CustomColours { get; set; } = new Dictionary();
+ public Dictionary CustomColours { get; } = new Dictionary();
public Dictionary ImageLookups = new Dictionary();
@@ -31,10 +31,12 @@ namespace osu.Game.Skinning
public readonly float[] ColumnSpacing;
public readonly float[] ColumnWidth;
public readonly float[] ExplosionWidth;
+ public readonly float[] HoldNoteLightWidth;
public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR;
public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR;
public bool ShowJudgementLine = true;
+ public bool KeysUnderNotes;
public LegacyManiaSkinConfiguration(int keys)
{
@@ -44,6 +46,7 @@ namespace osu.Game.Skinning
ColumnSpacing = new float[keys - 1];
ColumnWidth = new float[keys];
ExplosionWidth = new float[keys];
+ HoldNoteLightWidth = new float[keys];
ColumnLineWidth.AsSpan().Fill(2);
ColumnWidth.AsSpan().Fill(DEFAULT_COLUMN_SIZE);
diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
index 4990ca8e60..a99710ea96 100644
--- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs
@@ -34,6 +34,8 @@ namespace osu.Game.Skinning
HoldNoteHeadImage,
HoldNoteTailImage,
HoldNoteBodyImage,
+ HoldNoteLightImage,
+ HoldNoteLightScale,
ExplosionImage,
ExplosionScale,
ColumnLineColour,
@@ -50,5 +52,6 @@ namespace osu.Game.Skinning
Hit100,
Hit50,
Hit0,
+ KeysUnderNotes,
}
}
diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
index 1e6102eaa4..a9d88e77ad 100644
--- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
+++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs
@@ -97,10 +97,18 @@ namespace osu.Game.Skinning
currentConfig.ShowJudgementLine = pair.Value == "1";
break;
+ case "KeysUnderNotes":
+ currentConfig.KeysUnderNotes = pair.Value == "1";
+ break;
+
case "LightingNWidth":
parseArrayValue(pair.Value, currentConfig.ExplosionWidth);
break;
+ case "LightingLWidth":
+ parseArrayValue(pair.Value, currentConfig.HoldNoteLightWidth);
+ break;
+
case "WidthForNoteHeightScale":
float minWidth = float.Parse(pair.Value, CultureInfo.InvariantCulture) * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR;
if (minWidth > 0)
diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs
index 10fb476728..5caf07b554 100644
--- a/osu.Game/Skinning/LegacySkin.cs
+++ b/osu.Game/Skinning/LegacySkin.cs
@@ -220,6 +220,20 @@ namespace osu.Game.Skinning
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As(getManiaImage(existing, $"NoteImage{maniaLookup.TargetColumn}L"));
+ case LegacyManiaSkinConfigurationLookups.HoldNoteLightImage:
+ return SkinUtils.As(getManiaImage(existing, "LightingL"));
+
+ case LegacyManiaSkinConfigurationLookups.HoldNoteLightScale:
+ Debug.Assert(maniaLookup.TargetColumn != null);
+
+ if (GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value < 2.5m)
+ return SkinUtils.As(new Bindable(1));
+
+ if (existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] != 0)
+ return SkinUtils.As(new Bindable(existing.HoldNoteLightWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE));
+
+ return SkinUtils.As(new Bindable(existing.ColumnWidth[maniaLookup.TargetColumn.Value] / LegacyManiaSkinConfiguration.DEFAULT_COLUMN_SIZE));
+
case LegacyManiaSkinConfigurationLookups.KeyImage:
Debug.Assert(maniaLookup.TargetColumn != null);
return SkinUtils.As(getManiaImage(existing, $"KeyImage{maniaLookup.TargetColumn}"));
@@ -258,6 +272,9 @@ namespace osu.Game.Skinning
case LegacyManiaSkinConfigurationLookups.Hit300:
case LegacyManiaSkinConfigurationLookups.Hit300g:
return SkinUtils.As(getManiaImage(existing, maniaLookup.Lookup.ToString()));
+
+ case LegacyManiaSkinConfigurationLookups.KeysUnderNotes:
+ return SkinUtils.As(new Bindable(existing.KeysUnderNotes));
}
return null;
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index a55870aa6d..25a924c929 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Skinning
public void AddComboColours(params Color4[] colours) => comboColours.AddRange(colours);
- public Dictionary CustomColours { get; set; } = new Dictionary();
+ public Dictionary CustomColours { get; } = new Dictionary();
public readonly Dictionary ConfigDictionary = new Dictionary();
}
diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
index 6ada632850..e492069c5e 100644
--- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
+++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs
@@ -208,7 +208,7 @@ namespace osu.Game.Tests.Beatmaps
protected override Texture GetBackground() => throw new NotImplementedException();
- protected override Track GetTrack() => throw new NotImplementedException();
+ protected override Track GetBeatmapTrack() => throw new NotImplementedException();
protected override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
{
diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
index cdf9170701..bfcb2403c1 100644
--- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
+++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs
@@ -37,6 +37,6 @@ namespace osu.Game.Tests.Beatmaps
protected override Texture GetBackground() => null;
- protected override Track GetTrack() => null;
+ protected override Track GetBeatmapTrack() => null;
}
}
diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs
index bfbf7bb9da..baa7b27d28 100644
--- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs
+++ b/osu.Game/Tests/CleanRunHeadlessGameHost.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.Runtime.CompilerServices;
using osu.Framework.Platform;
namespace osu.Game.Tests
@@ -10,8 +11,15 @@ namespace osu.Game.Tests
///
public class CleanRunHeadlessGameHost : HeadlessGameHost
{
- public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true)
- : base(gameName, bindIPC, realtime)
+ ///
+ /// Create a new instance.
+ ///
+ /// An optional suffix which will isolate this host from others called from the same method source.
+ /// Whether to bind IPC channels.
+ /// Whether the host should be forced to run in realtime, rather than accelerated test time.
+ /// The name of the calling method, used for test file isolation and clean-up.
+ public CleanRunHeadlessGameHost(string gameSuffix = @"", bool bindIPC = false, bool realtime = true, [CallerMemberName] string callingMethodName = @"")
+ : base(callingMethodName + gameSuffix, bindIPC, realtime)
{
}
diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs
index 866fc215d6..4db5139813 100644
--- a/osu.Game/Tests/Visual/OsuTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuTestScene.cs
@@ -20,6 +20,7 @@ using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
+using osu.Game.Overlays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@@ -29,6 +30,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
+ [ExcludeFromDynamicCompile]
public abstract class OsuTestScene : TestScene
{
protected Bindable Beatmap { get; private set; }
@@ -135,6 +137,9 @@ namespace osu.Game.Tests.Visual
[Resolved]
protected AudioManager Audio { get; private set; }
+ [Resolved]
+ protected MusicController MusicController { get; private set; }
+
///
/// Creates the ruleset to be used for this test scene.
///
@@ -164,8 +169,8 @@ namespace osu.Game.Tests.Visual
rulesetDependencies?.Dispose();
- if (Beatmap?.Value.TrackLoaded == true)
- Beatmap.Value.Track.Stop();
+ if (MusicController?.TrackLoaded == true)
+ MusicController.CurrentTrack.Stop();
if (contextFactory.IsValueCreated)
contextFactory.Value.ResetDatabase();
@@ -219,7 +224,7 @@ namespace osu.Game.Tests.Visual
store?.Dispose();
}
- protected override Track GetTrack() => track;
+ protected override Track GetBeatmapTrack() => track;
public class TrackVirtualStore : AudioCollectionManager, ITrackStore
{
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index d1e2033596..1de0633d1f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -24,9 +24,9 @@
-
-
-
+
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 9b25eaab41..7187b48907 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -80,7 +80,7 @@
-
+