diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml
index bc2626d3d6..9e11ab6663 100644
--- a/.github/workflows/diffcalc.yml
+++ b/.github/workflows/diffcalc.yml
@@ -53,6 +53,7 @@ jobs:
diffcalc:
name: Run
runs-on: self-hosted
+ timeout-minutes: 1440
if: needs.metadata.outputs.continue == 'yes'
needs: metadata
strategy:
diff --git a/osu.Android.props b/osu.Android.props
index 8fad10d247..5a0e7479fa 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,11 +51,11 @@
-
-
+
+
-
+
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index dcb88efeb6..e2b40e9dc6 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -140,10 +140,10 @@ namespace osu.Desktop
switch (activity)
{
case UserActivity.InGame game:
- return game.Beatmap.ToString();
+ return game.BeatmapInfo.ToString();
case UserActivity.Editing edit:
- return edit.Beatmap.ToString();
+ return edit.BeatmapInfo.ToString();
case UserActivity.InLobby lobby:
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
index a458771550..d4c2c0f0af 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs
@@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
protected CatchSelectionBlueprintTestScene()
{
- EditorBeatmap = new EditorBeatmap(new CatchBeatmap());
- EditorBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = 0;
+ EditorBeatmap = new EditorBeatmap(new CatchBeatmap()) { Difficulty = { CircleSize = 0 } };
EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint
{
BeatLength = 100
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
index e3811b7669..cca3701a60 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs
@@ -5,6 +5,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Utils;
+using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Edit.Blueprints.Components;
@@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
protected override void AddHitObject(DrawableHitObject hitObject)
{
// Create nested bananas (but positions are not randomized because beatmap processing is not done).
- hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), Beatmap.Value.BeatmapInfo.BaseDifficulty);
+ hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
base.AddHitObject(hitObject);
}
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
index cd1fa31b61..981efc9a13 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs
@@ -4,9 +4,9 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Utils;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Edit.Blueprints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
@@ -23,11 +23,12 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
private JuiceStream lastObject => LastObject?.HitObject as JuiceStream;
- [BackgroundDependencyLoader]
- private void load()
+ protected override IBeatmap GetPlayableBeatmap()
{
- Beatmap.Value.BeatmapInfo.BaseDifficulty.SliderTickRate = 5;
- Beatmap.Value.BeatmapInfo.BaseDifficulty.SliderMultiplier = velocity * 10;
+ var playable = base.GetPlayableBeatmap();
+ playable.Difficulty.SliderTickRate = 5;
+ playable.Difficulty.SliderMultiplier = velocity * 10;
+ return playable;
}
[Test]
diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs
index 5e73a89069..155d033dd0 100644
--- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs
@@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
X = x,
Path = sliderPath,
};
- EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = velocity;
+ EditorBeatmap.Difficulty.SliderMultiplier = velocity;
EditorBeatmap.Add(hitObject);
EditorBeatmap.Update(hitObject);
Assert.That(hitObject.Velocity, Is.EqualTo(velocity));
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 540f02580f..f291bfed13 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
public IEnumerable CaughtObjects => this.ChildrenOfType();
- public TestCatcher(DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty)
+ public TestCatcher(DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo difficulty)
: base(droppedObjectTarget, difficulty)
{
}
@@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public class TestKiaiFruit : Fruit
{
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index 6abfbdbe21..7cae9b18b9 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private ScheduledDelegate addManyFruit;
- private BeatmapDifficulty beatmapDifficulty;
+ private IBeatmapDifficultyInfo beatmapDifficulty;
public TestSceneCatcherArea()
{
@@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private class TestCatcherArea : CatcherArea
{
- public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
+ public TestCatcherArea(IBeatmapDifficultyInfo beatmapDifficulty)
{
var droppedObjectContainer = new DroppedObjectContainer();
Add(droppedObjectContainer);
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index a891ec6c0a..87cc2c45e8 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
- double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
+ double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) / 2;
// Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins.
// This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible.
diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
index 82d76252d2..03a76f10ef 100644
--- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
return new CatchDifficultyAttributes { Mods = mods, Skills = skills };
// this is the same as osu!, so there's potential to share the implementation... maybe
- double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
+ double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
return new CatchDifficultyAttributes
{
@@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
{
- halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
+ halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f;
// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
- halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f);
+ halfCatcherWidth *= 1 - (Math.Max(0, beatmap.Difficulty.CircleSize - 5.5f) * 0.0625f);
return new Skill[]
{
diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
index 8c9f292aa9..046ba0ebce 100644
--- a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Edit
public class CatchEditorPlayfield : CatchPlayfield
{
// TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen.
- public CatchEditorPlayfield(BeatmapDifficulty difficulty)
+ public CatchEditorPlayfield(IBeatmapDifficultyInfo difficulty)
: base(difficulty)
{
}
diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
index 0344709d45..9a7528d90c 100644
--- a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
+++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs
@@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch.Edit
{
}
- protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
+ protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty);
}
}
diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
index d43e6f1c8b..ee10cf9711 100644
--- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
+++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs
@@ -128,11 +128,11 @@ namespace osu.Game.Rulesets.Catch.Objects
///
public int RandomSeed => (int)StartTime;
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
- TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
+ TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
index a8ad34fcbe..0d6925a83d 100644
--- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
+++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Objects
///
public double SpanDuration => Duration / this.SpanCount();
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
index 1e20643a08..df32d917ce 100644
--- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
+++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs
@@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Catch.UI
internal CatcherArea CatcherArea { get; private set; }
- private readonly BeatmapDifficulty difficulty;
+ private readonly IBeatmapDifficultyInfo difficulty;
- public CatchPlayfield(BeatmapDifficulty difficulty)
+ public CatchPlayfield(IBeatmapDifficultyInfo difficulty)
{
this.difficulty = difficulty;
}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 5cd85aac56..3745099010 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Catch.UI
private readonly DrawablePool caughtBananaPool;
private readonly DrawablePool caughtDropletPool;
- public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null)
+ public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo difficulty = null)
{
this.droppedObjectTarget = droppedObjectTarget;
@@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
///
- private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
+ private static Vector2 calculateScale(IBeatmapDifficultyInfo difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
///
/// Calculates the width of the area used for attempting catches in gameplay.
@@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Catch.UI
/// Calculates the width of the area used for attempting catches in gameplay.
///
/// The beatmap difficulty.
- public static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty));
+ public static float CalculateCatchWidth(IBeatmapDifficultyInfo difficulty) => CalculateCatchWidth(calculateScale(difficulty));
///
/// Determine if this catcher can catch a in the current position.
diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
index 8b6a074426..a8ec9f1d2f 100644
--- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs
@@ -27,14 +27,14 @@ namespace osu.Game.Rulesets.Catch.UI
: base(ruleset, beatmap, mods)
{
Direction.Value = ScrollingDirection.Down;
- TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
+ TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450);
}
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield);
- protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty);
+ protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.Difficulty);
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer();
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
index 26393c8edb..ebfbaccd31 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs
@@ -42,8 +42,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
{
IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
- var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
- var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ var roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize);
+ var roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty);
if (IsForCurrentRuleset)
{
@@ -71,9 +71,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
originalTargetColumns = TargetColumns;
}
- public static int GetColumnCountForNonConvert(BeatmapInfo beatmap)
+ public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo)
{
- var roundedCircleSize = Math.Round(beatmap.BaseDifficulty.CircleSize);
+ var roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize);
return (int)Math.Max(1, roundedCircleSize);
}
@@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
{
- BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
+ IBeatmapDifficultyInfo difficulty = original.Difficulty;
int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate);
Random = new FastRandom(seed);
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
index 26e5d381e2..380efff69f 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
StartTime = (int)Math.Round(hitObject.StartTime);
// This matches stable's calculation.
- EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier);
+ EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier);
SegmentDuration = (EndTime - StartTime) / SpanCount;
}
diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
index e643b82271..eaf0ea0f2b 100644
--- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
+++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (drainTime == 0)
drainTime = 10000;
- BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
+ IBeatmapDifficultyInfo difficulty = OriginalBeatmap.Difficulty;
conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
index a7a6677b68..9140e8afce 100644
--- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return new ManiaDifficultyAttributes { Mods = mods, Skills = skills };
HitWindows hitWindows = new ManiaHitWindows();
- hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
return new ManiaDifficultyAttributes
{
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
Mods = mods,
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future
GreatHitWindow = (int)Math.Ceiling(getHitWindow300(mods) / clockRate),
- ScoreMultiplier = getScoreMultiplier(beatmap, mods),
+ ScoreMultiplier = getScoreMultiplier(mods),
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
Skills = skills
};
@@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[]
{
- new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns)
+ new Strain(mods, ((ManiaBeatmap)Beatmap).TotalColumns)
};
protected override Mod[] DifficultyAdjustmentMods
@@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
- private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods)
+ private double getScoreMultiplier(Mod[] mods)
{
double scoreMultiplier = 1;
@@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
- var maniaBeatmap = (ManiaBeatmap)beatmap;
+ var maniaBeatmap = (ManiaBeatmap)Beatmap;
int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns;
if (diff > 0)
diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
index d9a278ef29..0290230490 100644
--- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
+++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs
@@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Mania
{
private FilterCriteria.OptionalRange keys;
- public bool Matches(BeatmapInfo beatmap)
+ public bool Matches(BeatmapInfo beatmapInfo)
{
- return !keys.HasFilter || (beatmap.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmap)));
+ return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo)));
}
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
index 43e876b7aa..c1937af7e4 100644
--- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
+++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Objects
///
private double tickSpacing = 50;
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
index 614a7b00c7..3b7da8d9ba 100644
--- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
+++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI
{
// Mania doesn't care about global velocity
p.Velocity = 1;
- p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
+ p.BaseBeatLength *= Beatmap.Difficulty.SliderMultiplier;
// For non-mania beatmap, speed changes should only happen through timing points
if (!isForCurrentRuleset)
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
index 6a3f168ee1..787807a8ea 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
public class CheckTooShortSpinnersTest
{
private CheckTooShortSpinners check;
- private BeatmapDifficulty difficulty;
+ private IBeatmapDifficultyInfo difficulty;
[SetUp]
public void Setup()
@@ -81,12 +81,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
assertTooShort(new List { spinnerHighOd }, difficultyHighOd);
}
- private void assertOk(List hitObjects, BeatmapDifficulty beatmapDifficulty)
+ private void assertOk(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty)
{
Assert.That(check.Run(getContext(hitObjects, beatmapDifficulty)), Is.Empty);
}
- private void assertVeryShort(List hitObjects, BeatmapDifficulty beatmapDifficulty)
+ private void assertVeryShort(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty)
{
var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList();
@@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateVeryShort);
}
- private void assertTooShort(List hitObjects, BeatmapDifficulty beatmapDifficulty)
+ private void assertTooShort(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty)
{
var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList();
@@ -102,12 +102,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateTooShort);
}
- private BeatmapVerifierContext getContext(List hitObjects, BeatmapDifficulty beatmapDifficulty)
+ private BeatmapVerifierContext getContext(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty)
{
var beatmap = new Beatmap
{
HitObjects = hitObjects,
- BeatmapInfo = new BeatmapInfo { BaseDifficulty = beatmapDifficulty }
+ BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(beatmapDifficulty) }
};
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
index 9af2a99470..851be2b2f2 100644
--- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[SetUp]
public void Setup() => Schedule(() =>
{
- editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
+ editorBeatmap.Difficulty.SliderMultiplier = 1;
editorBeatmap.ControlPointInfo.Clear();
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
index 1ac3ad9194..ed9da36b05 100644
--- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs
@@ -4,13 +4,11 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
-using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests.Mods
@@ -122,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4;
private bool objectWithIncreasedVisibilityHasIndex(int index)
- => Player.Mods.Value.OfType().Single().FirstObject == Player.ChildrenOfType().Single().HitObjects[index];
+ => Player.GameplayState.Mods.OfType().Single().FirstObject == Player.GameplayState.Beatmap.HitObjects[index];
private class TestOsuModHidden : OsuModHidden
{
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png
index a9b2d95d88..8e50cd0335 100755
Binary files a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png and b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png differ
diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
new file mode 100644
index 0000000000..49ac2cf80d
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini
@@ -0,0 +1,3 @@
+[General]
+Version: latest
+HitCircleOverlayAboveNumber: 0
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs
index 10d9d7ffde..79150a1941 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
Position = new Vector2(100, 300),
},
- accuracyHeatmap = new TestAccuracyHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo })
+ accuracyHeatmap = new TestAccuracyHeatmap(new ScoreInfo { BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo })
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
index f9dc9abd75..f09aad8b49 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs
@@ -17,6 +17,7 @@ using osu.Framework.Testing.Input;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Configuration;
+using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Screens.Play;
@@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public class TestSceneGameplayCursor : OsuSkinnableTestScene
{
[Cached]
- private GameplayBeatmap gameplayBeatmap;
+ private GameplayState gameplayState;
private OsuCursorContainer lastContainer;
@@ -40,7 +41,8 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneGameplayCursor()
{
- gameplayBeatmap = new GameplayBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
+ var ruleset = new OsuRuleset();
+ gameplayState = new GameplayState(CreateBeatmap(ruleset.RulesetInfo), ruleset, Array.Empty());
AddStep("change background colour", () =>
{
@@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddSliderStep("circle size", 0f, 10f, 0f, val =>
{
config.SetValue(OsuSetting.AutoCursorSize, true);
- gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val;
+ gameplayState.Beatmap.Difficulty.CircleSize = val;
Scheduler.AddOnce(() => loadContent(false));
});
@@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public void TestSizing(int circleSize, float userScale)
{
AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale));
- AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize);
+ AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize);
AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true));
AddStep("load content", () => loadContent());
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
index af67ab5839..a5629119b6 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
};
var hitWindows = new OsuHitWindows();
- hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
CreateModTest(new ModTestData
{
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests
};
var hitWindows = new OsuHitWindows();
- hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
CreateModTest(new ModTestData
{
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
index 77a68b714b..ececfb0586 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs
@@ -400,9 +400,9 @@ namespace osu.Game.Rulesets.Osu.Tests
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
{
HitObjects = hitObjects,
+ Difficulty = new BeatmapDifficulty { SliderTickRate = 3 },
BeatmapInfo =
{
- BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
Ruleset = new OsuRuleset().RulesetInfo
},
});
@@ -452,7 +452,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private class TestSpinner : Spinner
{
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
SpinsRequired = 1;
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
index 177a4f50a1..1b85e0efde 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs
@@ -412,7 +412,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private class TestSpinner : Spinner
{
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
SpinsRequired = 1;
diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
index 4c8d0b2ce6..17a611817c 100644
--- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0;
- double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate;
+ double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
int maxCombo = beatmap.HitObjects.Count;
// Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above)
@@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate)
{
HitWindows hitWindows = new OsuHitWindows();
- hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future
hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate;
diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs
index 0d0c3d9e69..f0aade1b7f 100644
--- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks
public IEnumerable Run(BeatmapVerifierContext context)
{
- double od = context.Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty;
+ double od = context.Beatmap.Difficulty.OverallDifficulty;
// These are meant to reflect the duration necessary for auto to score at least 1000 points on the spinner.
// It's difficult to eliminate warnings here, as auto achieving 1000 points depends on the approach angle on some spinners.
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
index 210d5e0403..bf70a63ab5 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs
@@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Mods
#region Reduce AR (IApplicableToDifficulty)
- public void ReadFromDifficulty(BeatmapDifficulty difficulty)
+ public void ReadFromDifficulty(IBeatmapDifficultyInfo difficulty)
{
}
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Mods
.Select(beat =>
{
var newCircle = new HitCircle();
- newCircle.ApplyDefaults(controlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty);
+ newCircle.ApplyDefaults(controlPointInfo, osuBeatmap.Difficulty);
newCircle.StartTime = beat;
return (OsuHitObject)newCircle;
}).ToList();
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index 36629fa41e..7c45b2bc07 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -122,11 +122,11 @@ namespace osu.Game.Rulesets.Osu.Objects
});
}
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
- TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN);
+ TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN);
// Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR.
// This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above.
diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs
index c4420b1e87..1d2666f46b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Slider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs
@@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Objects
Path.Version.ValueChanged += _ => updateNestedPositions();
}
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs
index a6aed2c00e..f893559548 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public double SpanDuration => slider.SpanDuration;
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
index 725dbe81fb..e7e64954e9 100644
--- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
+++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects
public int SpanIndex { get; set; }
public double SpanStartTime { get; set; }
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
index 194aa640f9..f85dc0d391 100644
--- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects
///
public int MaximumBonusSpins { get; protected set; } = 1;
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects
double secondsDuration = Duration / 1000;
- double minimumRotationsPerSecond = stable_matching_fudge * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5);
+ double minimumRotationsPerSecond = stable_matching_fudge * IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5);
SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond);
MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration);
diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
index b88bf9108b..e231550e3e 100644
--- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
+++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Replays
: base(beatmap, mods)
{
defaultHitWindows = new OsuHitWindows();
- defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ defaultHitWindows.SetDifficulty(Beatmap.Difficulty.OverallDifficulty);
}
#endregion
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
index c2db5f3f82..611ddd08eb 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private OsuPlayfield playfield { get; set; }
[Resolved(canBeNull: true)]
- private GameplayBeatmap gameplayBeatmap { get; set; }
+ private GameplayState gameplayState { get; set; }
[BackgroundDependencyLoader]
private void load(ISkinSource skin, OsuColour colours)
@@ -75,12 +75,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override void Update()
{
- if (playfield == null || gameplayBeatmap == null) return;
+ if (playfield == null || gameplayState == null) return;
DrawableHitObject kiaiHitObject = null;
// Check whether currently in a kiai section first. This is only done as an optimisation to avoid enumerating AliveObjects when not necessary.
- if (gameplayBeatmap.ControlPointInfo.EffectPointAt(Time.Current).KiaiMode)
+ if (gameplayState.Beatmap.ControlPointInfo.EffectPointAt(Time.Current).KiaiMode)
kiaiHitObject = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(isTracking);
kiaiSpewer.Active.Value = kiaiHitObject != null;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
index 3afd814174..d1c9b1bf92 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
@@ -35,8 +35,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private Drawable hitCircleSprite;
- protected Drawable HitCircleOverlay { get; private set; }
+ protected Container OverlayLayer { get; private set; }
+ private Drawable hitCircleOverlay;
private SkinnableSpriteText hitCircleText;
private readonly Bindable accentColour = new Bindable();
@@ -78,17 +79,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- HitCircleOverlay = new KiaiFlashingSprite
+ OverlayLayer = new Container
{
- Texture = overlayTexture,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- },
+ Child = hitCircleOverlay = new KiaiFlashingSprite
+ {
+ Texture = overlayTexture,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ }
};
if (hasNumber)
{
- AddInternal(hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
+ OverlayLayer.Add(hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40),
UseFullGlyphHeight = false,
@@ -102,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true;
if (overlayAboveNumber)
- ChangeInternalChildDepth(HitCircleOverlay, float.MinValue);
+ OverlayLayer.ChangeChildDepth(hitCircleOverlay, float.MinValue);
accentColour.BindTo(drawableObject.AccentColour);
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
@@ -147,8 +153,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
hitCircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
hitCircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
- HitCircleOverlay.FadeOut(legacy_fade_duration, Easing.Out);
- HitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
+ hitCircleOverlay.FadeOut(legacy_fade_duration, Easing.Out);
+ hitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
if (hasNumber)
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs
index 13ba42ba50..7de2b8c7fa 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
[Resolved(canBeNull: true)]
private DrawableHitObject drawableHitObject { get; set; }
- private Drawable proxiedHitCircleOverlay;
+ private Drawable proxiedOverlayLayer;
public LegacySliderHeadHitCircle()
: base("sliderstartcircle")
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
protected override void LoadComplete()
{
base.LoadComplete();
- proxiedHitCircleOverlay = HitCircleOverlay.CreateProxy();
+ proxiedOverlayLayer = OverlayLayer.CreateProxy();
if (drawableHitObject != null)
{
@@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private void onHitObjectApplied(DrawableHitObject drawableObject)
{
- Debug.Assert(proxiedHitCircleOverlay.Parent == null);
+ Debug.Assert(proxiedOverlayLayer.Parent == null);
// see logic in LegacyReverseArrow.
(drawableObject as DrawableSliderHead)?.DrawableSlider
- .OverlayElementContainer.Add(proxiedHitCircleOverlay.With(d => d.Depth = float.MinValue));
+ .OverlayElementContainer.Add(proxiedOverlayLayer.With(d => d.Depth = float.MinValue));
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
index cb769c31b8..24a660e69e 100644
--- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
+++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs
@@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Statistics
return;
// Todo: This should probably not be done like this.
- float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (playableBeatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5) / 5) / 2;
+ float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (playableBeatmap.Difficulty.CircleSize - 5) / 5) / 2;
foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)))
{
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
index 83bcc88e5f..d1d9ee9f4d 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
[Resolved(canBeNull: true)]
- private GameplayBeatmap beatmap { get; set; }
+ private GameplayState state { get; set; }
[Resolved]
private OsuConfigManager config { get; set; }
@@ -96,10 +96,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
float scale = userCursorScale.Value;
- if (autoCursorScale.Value && beatmap != null)
+ if (autoCursorScale.Value && state != null)
{
// if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier.
- scale *= GetScaleForCircleSize(beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
+ scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize);
}
cursorScale.Value = scale;
diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
index 9b73e644c5..ef8cf5cb53 100644
--- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs
@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Utils;
using System.Threading;
+using JetBrains.Annotations;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
@@ -46,11 +47,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
{
- if (!(original.BeatmapInfo.BaseDifficulty is TaikoMutliplierAppliedDifficulty))
+ if (!(original.Difficulty is TaikoMutliplierAppliedDifficulty))
{
// Rewrite the beatmap info to add the slider velocity multiplier
- original.BeatmapInfo = original.BeatmapInfo.Clone();
- original.BeatmapInfo.BaseDifficulty = new TaikoMutliplierAppliedDifficulty(original.BeatmapInfo.BaseDifficulty);
+ original.Difficulty = new TaikoMutliplierAppliedDifficulty(original.Difficulty);
}
Beatmap converted = base.ConvertBeatmap(original, cancellationToken);
@@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = taikoDuration,
- TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
+ TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4
};
}
@@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
case IHasDuration endTimeData:
{
- double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
+ double hitMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier;
yield return new Swell
{
@@ -164,10 +164,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
else
beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier;
- double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate;
+ double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate;
// The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll.
- double taikoVelocity = sliderScoringPointDistance * beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate;
+ double taikoVelocity = sliderScoringPointDistance * beatmap.Difficulty.SliderTickRate;
taikoDuration = (int)(distance / taikoVelocity * beatLength);
if (isForCurrentRuleset)
@@ -183,7 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
beatLength = timingPoint.BeatLength;
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
- tickSpacing = Math.Min(beatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, (double)taikoDuration / spans);
+ tickSpacing = Math.Min(beatLength / beatmap.Difficulty.SliderTickRate, (double)taikoDuration / spans);
return tickSpacing > 0
&& distance / osuVelocity * 1000 < 2 * beatLength;
@@ -193,11 +193,33 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
private class TaikoMutliplierAppliedDifficulty : BeatmapDifficulty
{
- public TaikoMutliplierAppliedDifficulty(BeatmapDifficulty difficulty)
+ public TaikoMutliplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty)
{
- difficulty.CopyTo(this);
- SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
+ CopyFrom(difficulty);
}
+
+ [UsedImplicitly]
+ public TaikoMutliplierAppliedDifficulty()
+ {
+ }
+
+ #region Overrides of BeatmapDifficulty
+
+ public override void CopyTo(BeatmapDifficulty other)
+ {
+ base.CopyTo(other);
+ if (!(other is TaikoMutliplierAppliedDifficulty))
+ SliderMultiplier /= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
+ }
+
+ public override void CopyFrom(IBeatmapDifficultyInfo other)
+ {
+ base.CopyFrom(other);
+ if (!(other is TaikoMutliplierAppliedDifficulty))
+ SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
+ }
+
+ #endregion
}
}
}
diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
index 18d06c069f..e755bb2325 100644
--- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
+++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs
@@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
starRating = rescale(starRating);
HitWindows hitWindows = new TaikoHitWindows();
- hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
return new TaikoDifficultyAttributes
{
diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
index b0634295d0..0318e32991 100644
--- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
+++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY;
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
index f7a1d130eb..0d6ce44255 100644
--- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
+++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs
@@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring
{
base.ApplyBeatmap(beatmap);
- hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98));
- hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120);
+ hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.5, 0.75, 0.98));
+ hpMissMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.0018, 0.0075, 0.0120);
}
protected override double GetHealthIncreaseFor(JudgementResult result)
diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs
index 6fc59ea0e8..fa49242675 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs
@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
}
[BackgroundDependencyLoader(true)]
- private void load(GameplayBeatmap gameplayBeatmap)
+ private void load(GameplayState gameplayState)
{
- if (gameplayBeatmap != null)
- ((IBindable)LastResult).BindTo(gameplayBeatmap.LastJudgementResult);
+ if (gameplayState != null)
+ ((IBindable)LastResult).BindTo(gameplayState.LastJudgementResult);
}
private bool passing;
diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
index 6a16f311bf..e1063e1071 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.UI
}
[BackgroundDependencyLoader(true)]
- private void load(TextureStore textures, GameplayBeatmap gameplayBeatmap)
+ private void load(TextureStore textures, GameplayState gameplayState)
{
InternalChildren = new[]
{
@@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Taiko.UI
animations[TaikoMascotAnimationState.Fail] = new TaikoMascotAnimation(TaikoMascotAnimationState.Fail),
};
- if (gameplayBeatmap != null)
- ((IBindable)LastResult).BindTo(gameplayBeatmap.LastJudgementResult);
+ if (gameplayState != null)
+ ((IBindable)LastResult).BindTo(gameplayState.LastJudgementResult);
}
protected override void LoadComplete()
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index a4bf8c92e3..b5e1fa204f 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -129,7 +129,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new LineBufferedReader(resStream))
{
- var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty;
+ var difficulty = decoder.Decode(stream).Difficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
index bcde899789..560e2ef894 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs
@@ -149,5 +149,32 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[5]).LoopType);
}
}
+
+ [Test]
+ public void TestDecodeLoopCount()
+ {
+ // all loop sequences in loop-count.osb have a total duration of 2000ms (fade in 0->1000ms, fade out 1000->2000ms).
+ const double loop_duration = 2000;
+
+ var decoder = new LegacyStoryboardDecoder();
+
+ using (var resStream = TestResources.OpenResource("loop-count.osb"))
+ using (var stream = new LineBufferedReader(resStream))
+ {
+ var storyboard = decoder.Decode(stream);
+
+ StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
+
+ // stable ensures that any loop command executes at least once, even if the loop count specified in the .osb is zero or negative.
+ StoryboardSprite zeroTimes = background.Elements.OfType().Single(s => s.Path == "zero-times.png");
+ Assert.That(zeroTimes.EndTime, Is.EqualTo(1000 + loop_duration));
+
+ StoryboardSprite oneTime = background.Elements.OfType().Single(s => s.Path == "one-time.png");
+ Assert.That(oneTime.EndTime, Is.EqualTo(4000 + loop_duration));
+
+ StoryboardSprite manyTimes = background.Elements.OfType().Single(s => s.Path == "many-times.png");
+ Assert.That(manyTimes.EndTime, Is.EqualTo(9000 + 40 * loop_duration));
+ }
+ }
}
}
diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
index 1fc3abef9a..9ec2f37569 100644
--- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeDifficulty()
{
var beatmap = decodeAsJson(normal);
- var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
+ var difficulty = beatmap.Difficulty;
Assert.AreEqual(6.5f, difficulty.DrainRate);
Assert.AreEqual(4, difficulty.CircleSize);
Assert.AreEqual(8, difficulty.OverallDifficulty);
@@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
processor.PreProcess();
foreach (var o in converted.HitObjects)
- o.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty);
+ o.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty);
processor.PostProcess();
var beatmap = converted.Serialize().Deserialize();
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index cba7f34ede..4cc71717ff 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- BeatmapSetInfo importedSet;
+ ILive importedSet;
using (var stream = File.OpenRead(tempPath))
{
@@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
File.Delete(tempPath);
- var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
deleteBeatmapSet(imported, osu);
}
@@ -172,8 +172,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
// but contents doesn't, so existing should still be used.
- Assert.IsTrue(imported.ID == importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
+ Assert.IsTrue(imported.ID == importedSecondTime.Value.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Value.Beatmaps.First().ID);
}
finally
{
@@ -226,8 +226,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
+ Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
}
finally
{
@@ -278,8 +278,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
+ Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
}
finally
{
@@ -329,8 +329,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
// check the newly "imported" beatmap is not the original.
- Assert.IsTrue(imported.ID != importedSecondTime.ID);
- Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID);
+ Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
+ Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID);
}
finally
{
@@ -570,8 +570,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await manager.Import(toImport);
Assert.NotNull(imported);
- Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID);
- Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID);
+ Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID);
+ Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID);
}
finally
{
@@ -706,7 +706,7 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
- Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
+ Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
}
finally
{
@@ -759,8 +759,8 @@ namespace osu.Game.Tests.Beatmaps.IO
ensureLoaded(osu);
- Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
- Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
+ Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
+ Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
}
finally
{
@@ -909,13 +909,13 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- var importedSet = await manager.Import(new ImportTask(temp));
+ var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
}
public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
@@ -924,13 +924,13 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- var importedSet = await manager.Import(new ImportTask(temp));
+ var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false);
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
}
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
@@ -945,13 +945,13 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
}
- private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap)
+ private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmapInfo)
{
return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo
{
OnlineScoreID = 2,
- Beatmap = beatmap,
- BeatmapInfoID = beatmap.ID
+ BeatmapInfo = beatmapInfo,
+ BeatmapInfoID = beatmapInfo.ID
}, new ImportScoreTest.TestArchiveReader());
}
diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs
new file mode 100644
index 0000000000..245981cd9b
--- /dev/null
+++ b/osu.Game.Tests/Database/GeneralUsageTests.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using NUnit.Framework;
+
+#nullable enable
+
+namespace osu.Game.Tests.Database
+{
+ [TestFixture]
+ public class GeneralUsageTests : RealmTest
+ {
+ ///
+ /// Just test the construction of a new database works.
+ ///
+ [Test]
+ public void TestConstructRealm()
+ {
+ RunTestWithRealm((realmFactory, _) => { realmFactory.CreateContext().Refresh(); });
+ }
+
+ [Test]
+ public void TestBlockOperations()
+ {
+ RunTestWithRealm((realmFactory, _) =>
+ {
+ using (realmFactory.BlockAllOperations())
+ {
+ }
+ });
+ }
+
+ [Test]
+ public void TestBlockOperationsWithContention()
+ {
+ RunTestWithRealm((realmFactory, _) =>
+ {
+ ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim();
+ ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim();
+
+ Task.Factory.StartNew(() =>
+ {
+ using (realmFactory.CreateContext())
+ {
+ hasThreadedUsage.Set();
+
+ stopThreadedUsage.Wait();
+ }
+ }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler);
+
+ hasThreadedUsage.Wait();
+
+ Assert.Throws(() =>
+ {
+ using (realmFactory.BlockAllOperations())
+ {
+ }
+ });
+
+ stopThreadedUsage.Set();
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs
new file mode 100644
index 0000000000..576f901c1a
--- /dev/null
+++ b/osu.Game.Tests/Database/RealmTest.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;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+using Nito.AsyncEx;
+using NUnit.Framework;
+using osu.Framework.Logging;
+using osu.Framework.Platform;
+using osu.Framework.Testing;
+using osu.Game.Database;
+
+#nullable enable
+
+namespace osu.Game.Tests.Database
+{
+ [TestFixture]
+ public abstract class RealmTest
+ {
+ private static readonly TemporaryNativeStorage storage;
+
+ static RealmTest()
+ {
+ storage = new TemporaryNativeStorage("realm-test");
+ storage.DeleteDirectory(string.Empty);
+ }
+
+ protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "")
+ {
+ AsyncContext.Run(() =>
+ {
+ var testStorage = storage.GetStorageForDirectory(caller);
+
+ using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ {
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
+ testAction(realmFactory, testStorage);
+
+ realmFactory.Dispose();
+
+ Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
+ realmFactory.Compact();
+ Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
+ }
+ });
+ }
+
+ protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "")
+ {
+ AsyncContext.Run(async () =>
+ {
+ var testStorage = storage.GetStorageForDirectory(caller);
+
+ using (var realmFactory = new RealmContextFactory(testStorage, caller))
+ {
+ Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}");
+ await testAction(realmFactory, testStorage);
+
+ realmFactory.Dispose();
+
+ Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}");
+ realmFactory.Compact();
+ Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}");
+ }
+ });
+ }
+
+ private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory)
+ {
+ try
+ {
+ using (var stream = testStorage.GetStream(realmFactory.Filename))
+ return stream?.Length ?? 0;
+ }
+ catch
+ {
+ // windows runs may error due to file still being open.
+ return 0;
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
index bd34eaff63..a40a6dac4c 100644
--- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing
BeatDivisor.Value = 1;
- composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
+ composer.EditorBeatmap.Difficulty.SliderMultiplier = 1;
composer.EditorBeatmap.ControlPointInfo.Clear();
composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 });
@@ -64,7 +64,7 @@ namespace osu.Game.Tests.Editing
[TestCase(2)]
public void TestSliderMultiplier(float multiplier)
{
- AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier);
+ AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier);
assertSnapDistance(100 * multiplier);
}
@@ -97,7 +97,7 @@ namespace osu.Game.Tests.Editing
assertDurationToDistance(500, 50);
assertDurationToDistance(1000, 100);
- AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
+ AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2);
assertDurationToDistance(500, 100);
assertDurationToDistance(1000, 200);
@@ -118,7 +118,7 @@ namespace osu.Game.Tests.Editing
assertDistanceToDuration(50, 500);
assertDistanceToDuration(100, 1000);
- AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
+ AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2);
assertDistanceToDuration(100, 500);
assertDistanceToDuration(200, 1000);
@@ -143,7 +143,7 @@ namespace osu.Game.Tests.Editing
assertSnappedDuration(200, 2000);
assertSnappedDuration(250, 3000);
- AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
+ AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2);
assertSnappedDuration(0, 0);
assertSnappedDuration(50, 0);
@@ -175,7 +175,7 @@ namespace osu.Game.Tests.Editing
assertSnappedDistance(200, 200);
assertSnappedDistance(250, 200);
- AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2);
+ AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2);
assertSnappedDistance(50, 0);
assertSnappedDistance(100, 0);
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
index 7264083338..296c5cef76 100644
--- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs
@@ -165,7 +165,7 @@ namespace osu.Game.Tests.Gameplay
{
var beatmap = new Beatmap
{
- BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } },
+ Difficulty = { DrainRate = 10 }
};
beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 0 });
@@ -200,7 +200,7 @@ namespace osu.Game.Tests.Gameplay
{
var beatmap = new Beatmap
{
- BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } },
+ Difficulty = { DrainRate = 10 }
};
for (double time = startTime; time <= endTime; time += 100)
diff --git a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs
index dab4825919..9926acf772 100644
--- a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs
+++ b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs
@@ -19,7 +19,7 @@ namespace osu.Game.Tests.Localisation
Title = "Romanised title",
TitleUnicode = "Unicode Title"
};
- var romanisableString = metadata.ToRomanisableString();
+ var romanisableString = metadata.GetDisplayTitleRomanisable();
Assert.AreEqual(metadata.ToString(), romanisableString.Romanised);
Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original);
@@ -33,7 +33,7 @@ namespace osu.Game.Tests.Localisation
Artist = "Romanised Artist",
Title = "Romanised title"
};
- var romanisableString = metadata.ToRomanisableString();
+ var romanisableString = metadata.GetDisplayTitleRomanisable();
Assert.AreEqual(romanisableString.Romanised, romanisableString.Original);
}
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
index 8ff2743b6a..ed86daf8b6 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
@@ -239,7 +239,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
match = shouldMatch;
}
- public bool Matches(BeatmapInfo beatmap) => match;
+ public bool Matches(BeatmapInfo beatmapInfo) => match;
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false;
}
}
diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
index a55bdd2df8..df42c70c87 100644
--- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
+++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs
@@ -256,7 +256,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
{
public string CustomValue { get; set; }
- public bool Matches(BeatmapInfo beatmap) => true;
+ public bool Matches(BeatmapInfo beatmapInfo) => true;
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
{
diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs
index aa29d76843..91c6b6c008 100644
--- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs
+++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs
@@ -42,7 +42,7 @@ namespace osu.Game.Tests.Online
AddAssert("response event fired", () => response != null);
- AddAssert("request has response", () => request.Result == response);
+ AddAssert("request has response", () => request.Response == response);
}
[Test]
diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
index d38294aba9..79767bc671 100644
--- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
+++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs
@@ -156,7 +156,7 @@ namespace osu.Game.Tests.Online
{
public TaskCompletionSource AllowImport = new TaskCompletionSource();
- public Task CurrentImportTask { get; private set; }
+ public Task> CurrentImportTask { get; private set; }
public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
: base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap)
@@ -194,7 +194,7 @@ namespace osu.Game.Tests.Online
this.testBeatmapManager = testBeatmapManager;
}
- public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
await testBeatmapManager.AllowImport.Task.ConfigureAwait(false);
return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false);
diff --git a/osu.Game.Tests/Resources/loop-count.osb b/osu.Game.Tests/Resources/loop-count.osb
new file mode 100644
index 0000000000..ec75e85ef1
--- /dev/null
+++ b/osu.Game.Tests/Resources/loop-count.osb
@@ -0,0 +1,15 @@
+osu file format v14
+
+[Events]
+Sprite,Background,TopCentre,"zero-times.png",320,240
+ L,1000,0
+ F,0,0,1000,0,1
+ F,0,1000,2000,1,0
+Sprite,Background,TopCentre,"one-time.png",320,240
+ L,4000,1
+ F,0,0,1000,0,1
+ F,0,1000,2000,1,0
+Sprite,Background,TopCentre,"many-times.png",320,240
+ L,9000,40
+ F,0,0,1000,0,1
+ F,0,1000,2000,1,0
diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
index cd7d744f53..2cd02329b7 100644
--- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
+++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs
@@ -141,7 +141,7 @@ namespace osu.Game.Tests.Scores.IO
var beatmapManager = osu.Dependencies.Get();
var scoreManager = osu.Dependencies.Get();
- beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID)));
+ beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID)));
Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true));
var secondImport = await LoadScoreIntoOsu(osu, imported);
@@ -181,7 +181,7 @@ namespace osu.Game.Tests.Scores.IO
{
var beatmapManager = osu.Dependencies.Get();
- score.Beatmap ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
+ score.BeatmapInfo ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
score.Ruleset ??= new OsuRuleset().RulesetInfo;
var scoreManager = osu.Dependencies.Get();
diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
index 7a9fc20426..b2600bb887 100644
--- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
+++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs
@@ -196,7 +196,7 @@ namespace osu.Game.Tests.Skins.IO
private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null)
{
var skinManager = osu.Dependencies.Get();
- return await skinManager.Import(archive);
+ return (await skinManager.Import(archive)).Value;
}
}
}
diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
index eff430ac25..f03cda1489 100644
--- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs
@@ -25,7 +25,7 @@ namespace osu.Game.Tests.Skins
private void load()
{
var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result;
- beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]);
+ beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]);
beatmap.LoadTrack();
}
diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
index 107a96292f..10f1ab31df 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins
private void load()
{
var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result;
- skin = skins.GetSkin(imported);
+ skin = skins.GetSkin(imported.Value);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs
new file mode 100644
index 0000000000..211543a881
--- /dev/null
+++ b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs
@@ -0,0 +1,127 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using ManagedBass.Fx;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Testing;
+using osu.Game.Audio.Effects;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+
+namespace osu.Game.Tests.Visual.Audio
+{
+ public class TestSceneAudioFilter : OsuTestScene
+ {
+ private OsuSpriteText lowpassText;
+ private AudioFilter lowpassFilter;
+
+ private OsuSpriteText highpassText;
+ private AudioFilter highpassFilter;
+
+ private Track track;
+
+ private WaveformTestBeatmap beatmap;
+
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ beatmap = new WaveformTestBeatmap(audio);
+ track = beatmap.LoadTrack();
+
+ Add(new FillFlowContainer
+ {
+ Children = new Drawable[]
+ {
+ lowpassFilter = new AudioFilter(audio.TrackMixer),
+ highpassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass),
+ lowpassText = new OsuSpriteText
+ {
+ Padding = new MarginPadding(20),
+ Text = $"Low Pass: {lowpassFilter.Cutoff.Value}hz",
+ Font = new FontUsage(size: 40)
+ },
+ new OsuSliderBar
+ {
+ Width = 500,
+ Height = 50,
+ Padding = new MarginPadding(20),
+ Current = { BindTarget = lowpassFilter.Cutoff }
+ },
+ highpassText = new OsuSpriteText
+ {
+ Padding = new MarginPadding(20),
+ Text = $"High Pass: {highpassFilter.Cutoff.Value}hz",
+ Font = new FontUsage(size: 40)
+ },
+ new OsuSliderBar
+ {
+ Width = 500,
+ Height = 50,
+ Padding = new MarginPadding(20),
+ Current = { BindTarget = highpassFilter.Cutoff }
+ }
+ }
+ });
+ lowpassFilter.Cutoff.ValueChanged += e => lowpassText.Text = $"Low Pass: {e.NewValue}hz";
+ highpassFilter.Cutoff.ValueChanged += e => highpassText.Text = $"High Pass: {e.NewValue}hz";
+ }
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("Play Track", () => track.Start());
+ waitTrackPlay();
+ }
+
+ [Test]
+ public void TestLowPass()
+ {
+ AddStep("Filter Sweep", () =>
+ {
+ lowpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
+ .CutoffTo(0, 2000, Easing.OutCubic);
+ });
+
+ waitTrackPlay();
+
+ AddStep("Filter Sweep (reverse)", () =>
+ {
+ lowpassFilter.CutoffTo(0).Then()
+ .CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
+ });
+
+ waitTrackPlay();
+ AddStep("Stop track", () => track.Stop());
+ }
+
+ [Test]
+ public void TestHighPass()
+ {
+ AddStep("Filter Sweep", () =>
+ {
+ highpassFilter.CutoffTo(0).Then()
+ .CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic);
+ });
+
+ waitTrackPlay();
+
+ AddStep("Filter Sweep (reverse)", () =>
+ {
+ highpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then()
+ .CutoffTo(0, 2000, Easing.OutCubic);
+ });
+
+ waitTrackPlay();
+
+ AddStep("Stop track", () => track.Stop());
+ }
+
+ private void waitTrackPlay() => AddWaitStep("Let track play", 10);
+ }
+}
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 1670d86545..693c66ccb0 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -232,7 +232,7 @@ namespace osu.Game.Tests.Visual.Background
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo
{
User = new User { Username = "osu!" },
- Beatmap = new TestBeatmap(Ruleset.Value).BeatmapInfo,
+ BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo,
Ruleset = Ruleset.Value,
})));
@@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Background
private void setupUserSettings()
{
AddUntilStep("Song select is current", () => songSelect.IsCurrentScreen());
- AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmap != null);
+ AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null);
AddStep("Set default user settings", () =>
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
new file mode 100644
index 0000000000..2258a209e2
--- /dev/null
+++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs
@@ -0,0 +1,62 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Input;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Screens.Edit;
+using osu.Game.Screens.Menu;
+using osu.Game.Screens.Select;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Editing
+{
+ public class TestSceneEditorSaving : OsuGameTestScene
+ {
+ private Editor editor => Game.ChildrenOfType().FirstOrDefault();
+
+ private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap));
+
+ ///
+ /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select.
+ ///
+ [Test]
+ public void TestNewBeatmapSaveThenLoad()
+ {
+ AddStep("set default beatmap", () => Game.Beatmap.SetDefault());
+
+ PushAndConfirm(() => new EditorLoader());
+
+ AddUntilStep("wait for editor load", () => editor != null);
+
+ AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
+
+ AddStep("Enter compose mode", () => InputManager.Key(Key.F1));
+ AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
+
+ AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
+ AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
+ AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
+
+ AddStep("Save and exit", () =>
+ {
+ InputManager.Keys(PlatformAction.Save);
+ InputManager.Key(Key.Escape);
+ });
+
+ AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
+
+ PushAndConfirm(() => new PlaySongSelect());
+
+ AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault);
+ AddStep("Open options", () => InputManager.Key(Key.F3));
+ AddStep("Enter editor", () => InputManager.Key(Key.Number5));
+
+ AddUntilStep("Wait for editor load", () => editor != null);
+ AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index e560c81fb2..7398527f57 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestEmptyLegacyBeatmapSkinFallsBack()
{
CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
+ AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded));
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
}
@@ -84,18 +85,18 @@ namespace osu.Game.Tests.Visual.Gameplay
Remove(expectedComponentsAdjustmentContainer);
return almostEqual(actualInfo, expectedInfo);
-
- static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
- other != null
- && info.Type == other.Type
- && info.Anchor == other.Anchor
- && info.Origin == other.Origin
- && Precision.AlmostEquals(info.Position, other.Position)
- && Precision.AlmostEquals(info.Scale, other.Scale)
- && Precision.AlmostEquals(info.Rotation, other.Rotation)
- && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual));
}
+ private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
+ other != null
+ && info.Type == other.Type
+ && info.Anchor == other.Anchor
+ && info.Origin == other.Origin
+ && Precision.AlmostEquals(info.Position, other.Position, 1)
+ && Precision.AlmostEquals(info.Scale, other.Scale)
+ && Precision.AlmostEquals(info.Rotation, other.Rotation)
+ && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual));
+
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
index 75a5eec6f7..3f10d7892d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs
@@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
- beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
+ beatmap.Difficulty.SliderMultiplier = 2;
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 5000);
@@ -196,7 +196,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var beatmap = createBeatmap();
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range });
- beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2;
+ beatmap.Difficulty.SliderMultiplier = 2;
createTest(beatmap);
AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 2000);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
index 73c6970482..814b41cdbc 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
var beatmap = new Beatmap
{
- BeatmapInfo = { BaseDifficulty = { ApproachRate = 9 } },
+ Difficulty = { ApproachRate = 9 },
};
for (int i = 0; i < 15; i++)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs
new file mode 100644
index 0000000000..4c48d52acd
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs
@@ -0,0 +1,108 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestScenePerformancePointsCounter : OsuTestScene
+ {
+ [Cached]
+ private GameplayState gameplayState;
+
+ [Cached]
+ private ScoreProcessor scoreProcessor;
+
+ private int iteration;
+ private PerformancePointsCounter counter;
+
+ public TestScenePerformancePointsCounter()
+ {
+ var ruleset = CreateRuleset();
+
+ Debug.Assert(ruleset != null);
+
+ var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
+ .GetPlayableBeatmap(ruleset.RulesetInfo);
+
+ gameplayState = new GameplayState(beatmap, ruleset);
+ scoreProcessor = new ScoreProcessor();
+ }
+
+ protected override Ruleset CreateRuleset() => new OsuRuleset();
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("Create counter", () =>
+ {
+ iteration = 0;
+
+ Child = counter = new PerformancePointsCounter
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(5),
+ };
+ });
+ }
+
+ [Test]
+ public void TestBasicCounting()
+ {
+ int previousValue = 0;
+
+ AddAssert("counter displaying zero", () => counter.Current.Value == 0);
+
+ AddRepeatStep("Add judgement", applyOneJudgement, 10);
+
+ AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
+ AddUntilStep("counter opaque", () => counter.Child.Alpha == 1);
+
+ AddStep("Revert judgement", () =>
+ {
+ previousValue = counter.Current.Value;
+
+ scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement()));
+ });
+
+ AddUntilStep("counter decreased", () => counter.Current.Value < previousValue);
+
+ AddStep("Add judgement", applyOneJudgement);
+
+ AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
+ }
+
+ private void applyOneJudgement()
+ {
+ var scoreInfo = gameplayState.Score.ScoreInfo;
+
+ scoreInfo.MaxCombo = iteration * 1000;
+ scoreInfo.Accuracy = 1;
+ scoreInfo.Statistics[HitResult.Great] = iteration * 1000;
+
+ scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject
+ {
+ StartTime = iteration * 10000,
+ }, new OsuJudgement())
+ {
+ Type = HitResult.Perfect,
+ });
+
+ iteration++;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 8160a62991..aee15a145c 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
- AddStep("retrieve mods", () => playerMod1 = (TestMod)player.Mods.Value.Single());
+ AddStep("retrieve mods", () => playerMod1 = (TestMod)player.GameplayState.Mods.Single());
AddAssert("game mods not applied", () => gameMod.Applied == false);
AddAssert("player mods applied", () => playerMod1.Applied);
@@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
- AddStep("retrieve mods", () => playerMod2 = (TestMod)player.Mods.Value.Single());
+ AddStep("retrieve mods", () => playerMod2 = (TestMod)player.GameplayState.Mods.Single());
AddAssert("game mods not applied", () => gameMod.Applied == false);
AddAssert("player has different mods", () => playerMod1 != playerMod2);
AddAssert("player mods applied", () => playerMod2.Applied);
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
index 1809332bce..5e2374cbcb 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online;
@@ -8,34 +9,29 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Scoring;
using osu.Game.Users;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Testing;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Screens.Ranking;
+using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
[TestFixture]
- public class TestSceneReplayDownloadButton : OsuTestScene
+ public class TestSceneReplayDownloadButton : OsuManualInputManagerTestScene
{
[Resolved]
private RulesetStore rulesets { get; set; }
private TestReplayDownloadButton downloadButton;
- public TestSceneReplayDownloadButton()
+ [Test]
+ public void TestDisplayStates()
{
- createButton(true);
- AddStep(@"downloading state", () => downloadButton.SetDownloadState(DownloadState.Downloading));
- AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
- AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
- createButton(false);
- createButtonNoScore();
- }
-
- private void createButton(bool withReplay)
- {
- AddStep(withReplay ? @"create button with replay" : "create button without replay", () =>
+ AddStep(@"create button with replay", () =>
{
- Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(withReplay))
+ Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(true))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -43,9 +39,81 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
+
+ AddStep(@"downloading state", () => downloadButton.SetDownloadState(DownloadState.Downloading));
+ AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable));
+ AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded));
}
- private void createButtonNoScore()
+ [Test]
+ public void TestButtonWithReplayStartsDownload()
+ {
+ bool downloadStarted = false;
+ bool downloadFinished = false;
+
+ AddStep(@"create button with replay", () =>
+ {
+ downloadStarted = false;
+ downloadFinished = false;
+
+ Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(true))
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+
+ downloadButton.State.BindValueChanged(state =>
+ {
+ switch (state.NewValue)
+ {
+ case DownloadState.Downloading:
+ downloadStarted = true;
+ break;
+ }
+
+ switch (state.OldValue)
+ {
+ case DownloadState.Downloading:
+ downloadFinished = true;
+ break;
+ }
+ });
+ });
+
+ AddUntilStep("wait for load", () => downloadButton.IsLoaded);
+
+ AddAssert("state is available", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+
+ AddStep("click button", () =>
+ {
+ InputManager.MoveMouseTo(downloadButton);
+ InputManager.Click(MouseButton.Left);
+ });
+
+ AddAssert("state entered downloading", () => downloadStarted);
+ AddUntilStep("state left downloading", () => downloadFinished);
+ }
+
+ [Test]
+ public void TestButtonWithoutReplay()
+ {
+ AddStep("create button without replay", () =>
+ {
+ Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(false))
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ };
+ });
+
+ AddUntilStep("wait for load", () => downloadButton.IsLoaded);
+
+ AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+ AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value);
+ }
+
+ [Test]
+ public void CreateButtonWithNoScore()
{
AddStep("create button with null score", () =>
{
@@ -57,6 +125,9 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddUntilStep("wait for load", () => downloadButton.IsLoaded);
+
+ AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
+ AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value);
}
private ScoreInfo getScoreInfo(bool replayAvailable)
@@ -78,6 +149,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public void SetDownloadState(DownloadState state) => State.Value = state;
+ public new Bindable State => base.State;
+
public TestReplayDownloadButton(ScoreInfo score)
: base(score)
{
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index 0a3fedaf8e..c8040f42f0 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -17,6 +18,8 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
@@ -38,7 +41,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder;
[Cached]
- private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
+ private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty());
[SetUp]
public void SetUp() => Schedule(() =>
@@ -57,7 +60,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Recorder = recorder = new TestReplayRecorder(new Score
{
Replay = replay,
- ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
+ ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
})
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
index dfd5e2dc58..3545fc96e8 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -13,6 +14,8 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
@@ -30,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly TestRulesetInputManager recordingManager;
[Cached]
- private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
+ private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty());
public TestSceneReplayRecording()
{
@@ -48,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Recorder = new TestReplayRecorder(new Score
{
Replay = replay,
- ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo }
+ ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
})
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
index 6f5f774758..b4de060578 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -25,6 +25,8 @@ using osu.Game.Online.Spectator;
using osu.Game.Replays;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.UI;
@@ -62,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private SpectatorClient spectatorClient { get; set; }
[Cached]
- private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
+ private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty());
[SetUp]
public void SetUp() => Schedule(() =>
@@ -354,7 +356,7 @@ namespace osu.Game.Tests.Visual.Gameplay
internal class TestReplayRecorder : ReplayRecorder
{
public TestReplayRecorder()
- : base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } })
+ : base(new Score { ScoreInfo = { BeatmapInfo = new BeatmapInfo() } })
{
}
diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
index f71d13ed35..bfea97410a 100644
--- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
+++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs
@@ -5,7 +5,6 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
-using osu.Framework.Screens;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK;
@@ -21,6 +20,8 @@ namespace osu.Game.Tests.Visual.Menus
protected OsuScreenStack IntroStack;
+ private IntroScreen intro;
+
protected IntroTestScene()
{
Children = new Drawable[]
@@ -39,7 +40,11 @@ namespace osu.Game.Tests.Visual.Menus
Position = new Vector2(0.5f),
}
};
+ }
+ [Test]
+ public virtual void TestPlayIntro()
+ {
AddStep("restart sequence", () =>
{
logo.FinishTransforms();
@@ -52,12 +57,12 @@ namespace osu.Game.Tests.Visual.Menus
RelativeSizeAxes = Axes.Both,
});
- IntroStack.Push(CreateScreen());
+ IntroStack.Push(intro = CreateScreen());
});
- AddUntilStep("wait for menu", () => IntroStack.CurrentScreen is MainMenu);
+ AddUntilStep("wait for menu", () => intro.DidLoadMenu);
}
- protected abstract IScreen CreateScreen();
+ protected abstract IntroScreen CreateScreen();
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs
index 107734cc8d..ffc99185fb 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
@@ -10,6 +9,6 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneIntroCircles : IntroTestScene
{
- protected override IScreen CreateScreen() => new IntroCircles();
+ protected override IntroScreen CreateScreen() => new IntroCircles();
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
index df79584167..8f01e0321b 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
@@ -10,6 +9,6 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneIntroTriangles : IntroTestScene
{
- protected override IScreen CreateScreen() => new IntroTriangles();
+ protected override IntroScreen CreateScreen() => new IntroTriangles();
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs
index 5f135febf4..9081be3dd6 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Screens.Menu;
@@ -11,10 +10,12 @@ namespace osu.Game.Tests.Visual.Menus
[TestFixture]
public class TestSceneIntroWelcome : IntroTestScene
{
- protected override IScreen CreateScreen() => new IntroWelcome();
+ protected override IntroScreen CreateScreen() => new IntroWelcome();
- public TestSceneIntroWelcome()
+ public override void TestPlayIntro()
{
+ base.TestPlayIntro();
+
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);
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
index 5fdadfc2fb..4754a73f83 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs
@@ -6,6 +6,7 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
using osu.Game.Overlays.Login;
namespace osu.Game.Tests.Visual.Menus
@@ -30,12 +31,25 @@ namespace osu.Game.Tests.Visual.Menus
}
[Test]
- public void TestBasicLogin()
+ public void TestLoginSuccess()
{
AddStep("logout", () => API.Logout());
AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password");
AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick());
}
+
+ [Test]
+ public void TestLoginFailure()
+ {
+ AddStep("logout", () =>
+ {
+ API.Logout();
+ ((DummyAPIAccess)API).FailNextLogin();
+ });
+
+ AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password");
+ AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick());
+ }
}
}
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
index 9037338e23..79dfe79299 100644
--- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
+++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("import beatmap with track", () =>
{
var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result;
- Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First());
+ Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First());
});
AddStep("bind to track change", () =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
index ff06d4d9c7..5032cdaec7 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Accuracy = 0.8,
MaxCombo = 500,
Combo = 250,
- Beatmap = beatmapInfo,
+ BeatmapInfo = beatmapInfo,
User = new User { Username = "Test user" },
Date = DateTimeOffset.Now,
OnlineScoreID = 12345,
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
index 0a8bda7ec0..99d5fd46e9 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Accuracy = 0.8,
MaxCombo = 500,
Combo = 250,
- Beatmap = beatmapInfo,
+ BeatmapInfo = beatmapInfo,
User = new User { Username = "Test user" },
Date = DateTimeOffset.Now,
OnlineScoreID = 12345,
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
index a8fda19c60..80217a7726 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
@@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
+using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@@ -68,6 +69,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
LoadScreen(dependenciesScreen = new DependenciesScreen(client));
});
+ AddUntilStep("wait for dependencies screen", () => Stack.CurrentScreen is DependenciesScreen);
+ AddUntilStep("wait for dependencies to start load", () => dependenciesScreen.LoadState > LoadState.NotLoaded);
AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
index e2baa82ba0..2706ff5ceb 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
@@ -6,11 +6,9 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Shapes;
+using osu.Framework.Configuration;
using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
-using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -28,12 +26,11 @@ using osu.Game.Scoring;
using osu.Game.Screens.Menu;
using osu.Game.Skinning;
using osu.Game.Utils;
-using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Navigation
{
[TestFixture]
- public class TestSceneOsuGame : OsuTestScene
+ public class TestSceneOsuGame : OsuGameTestScene
{
private IReadOnlyList requiredGameDependencies => new[]
{
@@ -83,34 +80,12 @@ namespace osu.Game.Tests.Visual.Navigation
typeof(PreviewTrackManager),
};
- private OsuGame game;
-
[Resolved]
private OsuGameBase gameBase { get; set; }
[Resolved]
private GameHost host { get; set; }
- [SetUpSteps]
- public void SetUpSteps()
- {
- AddStep("create game", () =>
- {
- Children = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- },
- };
-
- AddGame(game = new OsuGame());
- });
-
- AddUntilStep("wait for load", () => game.IsLoaded);
- }
-
[Test]
public void TestNullRulesetHandled()
{
@@ -123,6 +98,13 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("ruleset unchanged", () => ReferenceEquals(Ruleset.Value, ruleset));
}
+ [Test]
+ public void TestSwitchThreadExecutionMode()
+ {
+ AddStep("Change thread mode to multi threaded", () => { Game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.MultiThreaded); });
+ AddStep("Change thread mode to single thread", () => { Game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.SingleThread); });
+ }
+
[Test]
public void TestUnavailableRulesetHandled()
{
@@ -146,7 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation
{
foreach (var type in requiredGameDependencies)
{
- if (game.Dependencies.Get(type) == null)
+ if (Game.Dependencies.Get(type) == null)
throw new InvalidOperationException($"{type} has not been cached");
}
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
index f0ddefa51d..5f5ebfccfb 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs
@@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
},
}
- }).Result;
+ }).Result.Value;
});
AddAssert($"import {i} succeeded", () => imported != null);
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
index 52b577b402..aca7ada535 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = new OsuRuleset().RulesetInfo
},
}
- }).Result;
+ }).Result.Value;
});
}
@@ -130,9 +130,9 @@ namespace osu.Game.Tests.Visual.Navigation
{
Hash = Guid.NewGuid().ToString(),
OnlineScoreID = i,
- Beatmap = beatmap.Beatmaps.First(),
+ BeatmapInfo = beatmap.Beatmaps.First(),
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
- }).Result;
+ }).Result.Value;
});
AddAssert($"import {i} succeeded", () => imported != null);
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index aeb800f58a..ce437e7299 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -350,13 +350,13 @@ namespace osu.Game.Tests.Visual.Navigation
// since most overlays use a scroll container that absorbs on mouse down
NowPlayingOverlay nowPlayingOverlay = null;
- AddStep("enter menu", () => InputManager.Key(Key.Enter));
+ AddUntilStep("Wait for now playing load", () => (nowPlayingOverlay = Game.ChildrenOfType().FirstOrDefault()) != null);
- AddStep("get and press now playing hotkey", () =>
- {
- nowPlayingOverlay = Game.ChildrenOfType().Single();
- InputManager.Key(Key.F6);
- });
+ AddStep("enter menu", () => InputManager.Key(Key.Enter));
+ AddUntilStep("toolbar displayed", () => Game.Toolbar.State.Value == Visibility.Visible);
+
+ AddStep("open now playing", () => InputManager.Key(Key.F6));
+ AddUntilStep("now playing is visible", () => nowPlayingOverlay.State.Value == Visibility.Visible);
// drag tests
@@ -417,7 +417,7 @@ namespace osu.Game.Tests.Visual.Navigation
pushEscape(); // returns to osu! logo
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
- AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles);
+ AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroScreen);
AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape));
AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
AddStep("test dispose doesn't crash", () => Game.Dispose());
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
index f420ad976b..453e26ef96 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs
@@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Online
});
});
- AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value)));
+ AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.BeatmapInfo.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value)));
AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected);
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
index fd5c188b94..fe8e33f783 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs
@@ -58,10 +58,10 @@ namespace osu.Game.Tests.Visual.Online
var firstBeatmap = createBeatmap();
var secondBeatmap = createBeatmap();
- AddStep("set first set", () => successRate.Beatmap = firstBeatmap);
+ AddStep("set first set", () => successRate.BeatmapInfo = firstBeatmap);
AddAssert("ratings set", () => successRate.Graph.Metrics == firstBeatmap.Metrics);
- AddStep("set second set", () => successRate.Beatmap = secondBeatmap);
+ AddStep("set second set", () => successRate.BeatmapInfo = secondBeatmap);
AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics);
static BeatmapInfo createBeatmap() => new BeatmapInfo
@@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOnlyFailMetrics()
{
- AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
+ AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo
{
Metrics = new BeatmapMetrics
{
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestEmptyMetrics()
{
- AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo
+ AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo
{
Metrics = new BeatmapMetrics()
});
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
index 609e637914..9562b41363 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -18,6 +19,7 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
+using osu.Game.Overlays.Chat;
using osu.Game.Overlays.Chat.Selection;
using osu.Game.Overlays.Chat.Tabs;
using osu.Game.Users;
@@ -41,6 +43,9 @@ namespace osu.Game.Tests.Visual.Online
private Channel channel2 => channels[1];
private Channel channel3 => channels[2];
+ [CanBeNull]
+ private Func> onGetMessages;
+
[Resolved]
private GameHost host { get; set; }
@@ -79,6 +84,8 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("register request handling", () =>
{
+ onGetMessages = null;
+
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
@@ -102,6 +109,12 @@ namespace osu.Game.Tests.Visual.Online
}
return true;
+
+ case GetMessagesRequest getMessages:
+ var messages = onGetMessages?.Invoke(getMessages.Channel);
+ if (messages != null)
+ getMessages.TriggerSuccess(messages);
+ return true;
}
return false;
@@ -122,14 +135,37 @@ namespace osu.Game.Tests.Visual.Online
}
[Test]
- public void TestSelectingChannelClosesSelector()
+ public void TestChannelSelection()
{
AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible);
+ AddStep("Setup get message response", () => onGetMessages = channel =>
+ {
+ if (channel == channel1)
+ {
+ return new List
+ {
+ new Message(1)
+ {
+ ChannelId = channel1.Id,
+ Content = "hello from channel 1!",
+ Sender = new User
+ {
+ Id = 2,
+ Username = "test_user"
+ }
+ }
+ };
+ }
+
+ return null;
+ });
AddStep("Join channel 1", () => channelManager.JoinChannel(channel1));
AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1]));
AddAssert("Current channel is channel 1", () => currentChannel == channel1);
+ AddUntilStep("Loading spinner hidden", () => chatOverlay.ChildrenOfType().All(spinner => !spinner.IsPresent));
+ AddAssert("Channel message shown", () => chatOverlay.ChildrenOfType().Count() == 1);
AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
index 5dca218531..513631a221 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Online
{
PP = 1047.21,
Rank = ScoreRank.SH,
- Beatmap = new BeatmapInfo
+ BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online
{
PP = 134.32,
Rank = ScoreRank.A,
- Beatmap = new BeatmapInfo
+ BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
@@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online
{
PP = 96.83,
Rank = ScoreRank.S,
- Beatmap = new BeatmapInfo
+ BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
@@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Online
var noPPScore = new ScoreInfo
{
Rank = ScoreRank.B,
- Beatmap = new BeatmapInfo
+ BeatmapInfo = new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
index 9051c71fc6..deb9a22184 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
- importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result;
+ importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value;
});
AddStep("load room", () =>
@@ -137,11 +137,11 @@ namespace osu.Game.Tests.Visual.Playlists
InputManager.Click(MouseButton.Left);
});
- AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize == 1);
+ AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1);
AddStep("re-import original beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait());
- AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize != 1);
+ AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1);
}
private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
index a5e2f02f31..df8500fab2 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
@@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Ranking
Id = 2,
Username = "peppy",
},
- Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
+ BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo,
Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() },
TotalScore = 2845370,
Accuracy = accuracy,
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
index 5180854aba..899f351a2a 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Ranking
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
{
- Beatmap = createTestBeatmap(author)
+ BeatmapInfo = createTestBeatmap(author)
}));
}
@@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Ranking
AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true)
{
- Beatmap = createTestBeatmap(author)
+ BeatmapInfo = createTestBeatmap(author)
}));
AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name"));
@@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking
{
AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)
{
- Beatmap = createTestBeatmap(null)
+ BeatmapInfo = createTestBeatmap(null)
}));
AddAssert("mapped by text not present", () =>
@@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Ranking
showPanel(new TestScoreInfo(ruleset.RulesetInfo)
{
Mods = mods,
- Beatmap = beatmap,
+ BeatmapInfo = beatmap,
Date = default,
});
});
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
index 631455b727..8d5d0ba8c7 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
@@ -337,8 +337,8 @@ namespace osu.Game.Tests.Visual.Ranking
public UnrankedSoloResultsScreen(ScoreInfo score)
: base(score, true)
{
- Score.Beatmap.OnlineBeatmapID = 0;
- Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending;
+ Score.BeatmapInfo.OnlineBeatmapID = 0;
+ Score.BeatmapInfo.Status = BeatmapSetOnlineStatus.Pending;
}
protected override void LoadComplete()
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
index 6f3b3028be..b7b7407428 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs
@@ -221,6 +221,8 @@ namespace osu.Game.Tests.Visual.Ranking
list.SelectedScore.Value = middleScore;
});
+ AddUntilStep("wait for all scores to be visible", () => list.ChildrenOfType().All(t => t.IsPresent));
+
assertScoreState(highestScore, false);
assertScoreState(middleScore, true);
assertScoreState(lowestScore, false);
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
index 168d9fafcf..1effe52608 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Threading;
using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Settings.Sections.Input;
using osuTK.Input;
@@ -230,6 +231,22 @@ namespace osu.Game.Tests.Visual.Settings
AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding);
}
+ [Test]
+ public void TestFilteringHidesResetSectionButtons()
+ {
+ SearchTextBox searchTextBox = null;
+
+ AddStep("add any search term", () =>
+ {
+ searchTextBox = panel.ChildrenOfType().Single();
+ searchTextBox.Current.Value = "chat";
+ });
+ AddUntilStep("all reset section bindings buttons hidden", () => panel.ChildrenOfType().All(button => button.Alpha == 0));
+
+ AddStep("clear search term", () => searchTextBox.Current.Value = string.Empty);
+ AddUntilStep("all reset section bindings buttons shown", () => panel.ChildrenOfType().All(button => button.Alpha == 1));
+ }
+
private void checkBinding(string name, string keyName)
{
AddAssert($"Check {name} is bound to {keyName}", () =>
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
index dcc2111ad3..4538e36c5e 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs
@@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestNoMod()
{
- AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
+ AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo);
AddStep("no mods selected", () => SelectedMods.Value = Array.Empty());
@@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestManiaFirstBarText()
{
- AddStep("set beatmap", () => advancedStats.Beatmap = new BeatmapInfo
+ AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo
{
Ruleset = rulesets.GetRuleset(3),
BaseDifficulty = new BeatmapDifficulty
@@ -84,11 +84,11 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestEasyMod()
{
- AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
+ AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo);
AddStep("select EZ mod", () =>
{
- var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
+ var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
SelectedMods.Value = new[] { ruleset.CreateMod() };
});
@@ -101,11 +101,11 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestHardRockMod()
{
- AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
+ AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo);
AddStep("select HR mod", () =>
{
- var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
+ var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
SelectedMods.Value = new[] { ruleset.CreateMod() };
});
@@ -118,13 +118,13 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestUnchangedDifficultyAdjustMod()
{
- AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
+ AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo);
AddStep("select unchanged Difficulty Adjust mod", () =>
{
- var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
+ var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
var difficultyAdjustMod = ruleset.CreateMod();
- difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty);
+ difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.BaseDifficulty);
SelectedMods.Value = new[] { difficultyAdjustMod };
});
@@ -137,13 +137,13 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestChangedDifficultyAdjustMod()
{
- AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo);
+ AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo);
AddStep("select changed Difficulty Adjust mod", () =>
{
- var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
+ var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
var difficultyAdjustMod = ruleset.CreateMod();
- var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
+ var originalDifficulty = advancedStats.BeatmapInfo.BaseDifficulty;
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f;
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 78ddfa9ed2..66f15670f5 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private readonly Stack selectedSets = new Stack();
private readonly HashSet eagerSelectedIDs = new HashSet();
- private BeatmapInfo currentSelection => carousel.SelectedBeatmap;
+ private BeatmapInfo currentSelection => carousel.SelectedBeatmapInfo;
private const int set_count = 5;
@@ -75,11 +75,11 @@ namespace osu.Game.Tests.Visual.SongSelect
{
for (int i = 0; i < 3; i++)
{
- AddStep("store selection", () => selection = carousel.SelectedBeatmap);
+ AddStep("store selection", () => selection = carousel.SelectedBeatmapInfo);
if (isIterating)
- AddUntilStep("selection changed", () => carousel.SelectedBeatmap != selection);
+ AddUntilStep("selection changed", () => carousel.SelectedBeatmapInfo != selection);
else
- AddUntilStep("selection not changed", () => carousel.SelectedBeatmap == selection);
+ AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo == selection);
}
}
}
@@ -387,7 +387,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("Set non-empty mode filter", () =>
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false));
- AddAssert("Something is selected", () => carousel.SelectedBeatmap != null);
+ AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null);
}
///
@@ -562,7 +562,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("filter to ruleset 0", () =>
carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false));
AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false));
- AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmap.RulesetID == 0);
+ AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.RulesetID == 0);
AddStep("remove mixed set", () =>
{
@@ -653,7 +653,7 @@ namespace osu.Game.Tests.Visual.SongSelect
carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false);
});
- AddAssert("selection lost", () => carousel.SelectedBeatmap == null);
+ AddAssert("selection lost", () => carousel.SelectedBeatmapInfo == null);
AddStep("Restore different ruleset filter", () =>
{
@@ -661,7 +661,7 @@ namespace osu.Game.Tests.Visual.SongSelect
eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID);
});
- AddAssert("selection changed", () => carousel.SelectedBeatmap != manySets.First().Beatmaps.First());
+ AddAssert("selection changed", () => carousel.SelectedBeatmapInfo != manySets.First().Beatmaps.First());
}
AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2);
@@ -763,9 +763,9 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () =>
{
if (diff != null)
- return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First();
+ return carousel.SelectedBeatmapInfo == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First();
- return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap);
+ return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmapInfo);
});
private void setSelected(int set, int diff) =>
@@ -800,7 +800,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation;
- if (!selectedSets.Any() && carousel.SelectedBeatmap != null)
+ if (!selectedSets.Any() && carousel.SelectedBeatmapInfo != null)
selectedSets.Push(carousel.SelectedBeatmapSet);
carousel.SelectNextRandom();
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
index b4544fbc85..d5b4fb9a80 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestAllMetrics()
{
- AddStep("all metrics", () => details.Beatmap = new BeatmapInfo
+ AddStep("all metrics", () => details.BeatmapInfo = new BeatmapInfo
{
BeatmapSet = new BeatmapSetInfo
{
@@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestAllMetricsExceptSource()
{
- AddStep("all except source", () => details.Beatmap = new BeatmapInfo
+ AddStep("all except source", () => details.BeatmapInfo = new BeatmapInfo
{
BeatmapSet = new BeatmapSetInfo
{
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestOnlyRatings()
{
- AddStep("ratings", () => details.Beatmap = new BeatmapInfo
+ AddStep("ratings", () => details.BeatmapInfo = new BeatmapInfo
{
BeatmapSet = new BeatmapSetInfo
{
@@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestOnlyFailsAndRetries()
{
- AddStep("fails retries", () => details.Beatmap = new BeatmapInfo
+ AddStep("fails retries", () => details.BeatmapInfo = new BeatmapInfo
{
Version = "Only Retries and Fails",
Metadata = new BeatmapMetadata
@@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestNoMetrics()
{
- AddStep("no metrics", () => details.Beatmap = new BeatmapInfo
+ AddStep("no metrics", () => details.BeatmapInfo = new BeatmapInfo
{
Version = "No Metrics",
Metadata = new BeatmapMetadata
@@ -166,13 +166,13 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestNullBeatmap()
{
- AddStep("null beatmap", () => details.Beatmap = null);
+ AddStep("null beatmap", () => details.BeatmapInfo = null);
}
[Test]
public void TestOnlineMetrics()
{
- AddStep("online ratings/retries/fails", () => details.Beatmap = new BeatmapInfo
+ AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new BeatmapInfo
{
OnlineBeatmapID = 162,
});
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
index 29815ce9ff..13b769c80a 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs
@@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.SongSelect
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
- leaderboard.Beatmap = beatmapInfo;
+ leaderboard.BeatmapInfo = beatmapInfo;
});
clearScores();
@@ -186,7 +186,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void checkCount(int expected) =>
AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType().Count() == expected);
- private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmap)
+ private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
{
return new[]
{
@@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 6602580,
@@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 4608074,
@@ -235,7 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 1014222,
@@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 1541390,
@@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 2243452,
@@ -292,7 +292,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 2705430,
@@ -311,7 +311,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 7151382,
@@ -330,7 +330,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 2051389,
@@ -349,7 +349,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 6169483,
@@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect
MaxCombo = 244,
TotalScore = 1707827,
//Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
- Beatmap = beatmap,
+ BeatmapInfo = beatmapInfo,
User = new User
{
Id = 6702666,
@@ -385,7 +385,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void showBeatmapWithStatus(BeatmapSetOnlineStatus status)
{
- leaderboard.Beatmap = new BeatmapInfo
+ leaderboard.BeatmapInfo = new BeatmapInfo
{
OnlineBeatmapID = 1113057,
Status = status,
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
index 53cb628bb3..c22b6a54e9 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs
@@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect
}).ToList()
};
- return Game.BeatmapManager.Import(beatmapSet).Result;
+ return Game.BeatmapManager.Import(beatmapSet).Result.Value;
}
private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null);
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 102e5ee425..067f1cabb4 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select next and enter", () =>
{
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType()
- .First(b => ((CarouselBeatmap)b.Item).Beatmap != songSelect.Carousel.SelectedBeatmap));
+ .First(b => ((CarouselBeatmap)b.Item).BeatmapInfo != songSelect.Carousel.SelectedBeatmapInfo));
InputManager.Click(MouseButton.Left);
@@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select next and enter", () =>
{
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType()
- .First(b => ((CarouselBeatmap)b.Item).Beatmap != songSelect.Carousel.SelectedBeatmap));
+ .First(b => ((CarouselBeatmap)b.Item).BeatmapInfo != songSelect.Carousel.SelectedBeatmapInfo));
InputManager.PressButton(MouseButton.Left);
@@ -312,7 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
createSongSelect();
addRulesetImportStep(2);
- AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
+ AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
}
[Test]
@@ -322,13 +322,13 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(2);
addRulesetImportStep(2);
addRulesetImportStep(1);
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2);
changeRuleset(1);
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 1);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 1);
changeRuleset(0);
- AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null);
+ AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
}
[Test]
@@ -338,7 +338,7 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(2);
addRulesetImportStep(2);
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2);
addRulesetImportStep(0);
addRulesetImportStep(0);
@@ -355,7 +355,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Beatmap.Value = manager.GetWorkingBeatmap(target);
});
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target));
// this is an important check, to make sure updateComponentFromBeatmap() was actually run
AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target));
@@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(2);
addRulesetImportStep(2);
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2);
addRulesetImportStep(0);
addRulesetImportStep(0);
@@ -385,7 +385,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0);
});
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target));
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target));
AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0);
@@ -444,7 +444,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{
createSongSelect();
addManyTestMaps();
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
bool startRequested = false;
@@ -473,13 +473,13 @@ namespace osu.Game.Tests.Visual.SongSelect
// used for filter check below
AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono");
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
- AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmap == null);
+ AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
BeatmapInfo target = null;
@@ -494,7 +494,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Beatmap.Value = manager.GetWorkingBeatmap(target);
});
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
AddAssert("selected only shows expected ruleset (plus converts)", () =>
{
@@ -502,16 +502,16 @@ namespace osu.Game.Tests.Visual.SongSelect
// special case for converts checked here.
return selectedPanel.ChildrenOfType().All(i =>
- i.IsFiltered || i.Item.Beatmap.Ruleset.ID == targetRuleset || i.Item.Beatmap.Ruleset.ID == 0);
+ i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0);
});
- AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID);
+ AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = string.Empty);
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
- AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmap.OnlineBeatmapID == target.OnlineBeatmapID);
+ AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
}
[Test]
@@ -522,13 +522,13 @@ namespace osu.Game.Tests.Visual.SongSelect
changeRuleset(0);
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono");
AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap);
- AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmap == null);
+ AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
BeatmapInfo target = null;
@@ -540,15 +540,15 @@ namespace osu.Game.Tests.Visual.SongSelect
Beatmap.Value = manager.GetWorkingBeatmap(target);
});
- AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null);
+ AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
- AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID);
+ AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID);
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nononoo");
AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap);
- AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmap == null);
+ AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmapInfo == null);
}
[Test]
@@ -581,9 +581,9 @@ namespace osu.Game.Tests.Visual.SongSelect
createSongSelect();
addRulesetImportStep(0);
AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last()));
- AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID);
+ AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmapInfo.ID);
AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First()));
- AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID);
+ AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmapInfo.ID == previousID);
}
[Test]
@@ -641,7 +641,7 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.Click(MouseButton.Left);
});
- AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmap == filteredBeatmap);
+ AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmapInfo == filteredBeatmap);
}
[Test]
@@ -714,10 +714,11 @@ namespace osu.Game.Tests.Visual.SongSelect
});
FilterableDifficultyIcon difficultyIcon = null;
- AddStep("Find an icon for different ruleset", () =>
+ AddUntilStep("Find an icon for different ruleset", () =>
{
difficultyIcon = set.ChildrenOfType()
- .First(icon => icon.Item.Beatmap.Ruleset.ID == 3);
+ .FirstOrDefault(icon => icon.Item.BeatmapInfo.Ruleset.ID == 3);
+ return difficultyIcon != null;
});
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
@@ -735,7 +736,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
- AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmap.BeatmapSet.ID == previousSetID);
+ AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet.ID == previousSetID);
AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3);
}
@@ -751,7 +752,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import huge difficulty count map", () =>
{
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray();
- imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result;
+ imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result.Value;
});
AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First()));
@@ -767,7 +768,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("Find group icon for different ruleset", () =>
{
groupIcon = set.ChildrenOfType()
- .First(icon => icon.Items.First().Beatmap.Ruleset.ID == 3);
+ .First(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3);
});
AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0);
@@ -781,7 +782,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
- AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().Beatmap));
+ AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().BeatmapInfo));
}
[Test]
@@ -805,7 +806,7 @@ namespace osu.Game.Tests.Visual.SongSelect
songSelect.PresentScore(new ScoreInfo
{
User = new User { Username = "woo" },
- Beatmap = getPresentBeatmap(),
+ BeatmapInfo = getPresentBeatmap(),
Ruleset = getPresentBeatmap().Ruleset
});
});
@@ -837,7 +838,7 @@ namespace osu.Game.Tests.Visual.SongSelect
songSelect.PresentScore(new ScoreInfo
{
User = new User { Username = "woo" },
- Beatmap = getPresentBeatmap(),
+ BeatmapInfo = getPresentBeatmap(),
Ruleset = getPresentBeatmap().Ruleset
});
});
@@ -856,7 +857,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.FindIndex(b => b == info);
- private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap);
+ private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo);
private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon)
{
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
index 2e30ed9827..189b143a35 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs
@@ -37,7 +37,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private ScoreManager scoreManager;
private readonly List importedScores = new List();
- private BeatmapInfo beatmap;
+
+ private BeatmapInfo beatmapInfo;
[Cached]
private readonly DialogOverlay dialogOverlay;
@@ -55,7 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = BeatmapLeaderboardScope.Local,
- Beatmap = new BeatmapInfo
+ BeatmapInfo = new BeatmapInfo
{
ID = 1,
Metadata = new BeatmapMetadata
@@ -84,15 +85,15 @@ namespace osu.Game.Tests.Visual.UserInterface
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler));
- beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0];
+ beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0];
for (int i = 0; i < 50; i++)
{
var score = new ScoreInfo
{
OnlineScoreID = i,
- Beatmap = beatmap,
- BeatmapInfoID = beatmap.ID,
+ BeatmapInfo = beatmapInfo,
+ BeatmapInfoID = beatmapInfo.ID,
Accuracy = RNG.NextDouble(),
TotalScore = RNG.Next(1, 1000000),
MaxCombo = RNG.Next(1, 1000),
@@ -100,7 +101,7 @@ namespace osu.Game.Tests.Visual.UserInterface
User = new User { Username = "TestUser" },
};
- importedScores.Add(scoreManager.Import(score).Result);
+ importedScores.Add(scoreManager.Import(score).Result.Value);
}
return dependencies;
@@ -115,7 +116,7 @@ namespace osu.Game.Tests.Visual.UserInterface
leaderboard.Scores = null;
leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables
- leaderboard.Beatmap = beatmap;
+ leaderboard.BeatmapInfo = beatmapInfo;
leaderboard.RefreshScores(); // Required in the case that the beatmap hasn't changed
});
@@ -161,6 +162,8 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for fetch", () => leaderboard.Scores != null);
+
AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID));
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuFont.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuFont.cs
new file mode 100644
index 0000000000..eedafce271
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuFont.cs
@@ -0,0 +1,77 @@
+// 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.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneOsuFont : OsuTestScene
+ {
+ private OsuSpriteText spriteText;
+
+ private readonly BindableBool useAlternates = new BindableBool();
+ private readonly Bindable weight = new Bindable(FontWeight.Regular);
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Child = spriteText = new OsuSpriteText
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ RelativeSizeAxes = Axes.X,
+ AllowMultiline = true,
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ useAlternates.BindValueChanged(_ => updateFont());
+ weight.BindValueChanged(_ => updateFont(), true);
+ }
+
+ private void updateFont()
+ {
+ FontUsage usage = useAlternates.Value ? OsuFont.TorusAlternate : OsuFont.Torus;
+ spriteText.Font = usage.With(size: 40, weight: weight.Value);
+ }
+
+ [Test]
+ public void TestTorusAlternates()
+ {
+ AddStep("set all ASCII letters", () => spriteText.Text = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ
+abcdefghijklmnopqrstuvwxyz");
+ AddStep("set all alternates", () => spriteText.Text = @"A Á Ă Â Ä À Ā Ą Å Ã
+Æ B D Ð Ď Đ E É Ě Ê
+Ë Ė È Ē Ę F G Ğ Ģ Ġ
+H I Í Î Ï İ Ì Ī Į K
+Ķ O Œ P Þ Q R Ŕ Ř Ŗ
+T Ŧ Ť Ţ Ț V W Ẃ Ŵ Ẅ
+Ẁ X Y Ý Ŷ Ÿ Ỳ a á ă
+â ä à ā ą å ã æ b d
+ď đ e é ě ê ë ė è ē
+ę f g ğ ģ ġ k ķ m n
+ń ň ņ ŋ ñ o œ p þ q
+t ŧ ť ţ ț u ú û ü ù
+ű ū ų ů w ẃ ŵ ẅ ẁ x
+y ý ŷ ÿ ỳ");
+
+ AddToggleStep("toggle alternates", alternates => useAlternates.Value = alternates);
+
+ addSetWeightStep(FontWeight.Light);
+ addSetWeightStep(FontWeight.Regular);
+ addSetWeightStep(FontWeight.SemiBold);
+ addSetWeightStep(FontWeight.Bold);
+
+ void addSetWeightStep(FontWeight newWeight) => AddStep($"set weight {newWeight}", () => weight.Value = newWeight);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
index 198cc70e01..74cd675a05 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs
@@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var req = new GetBeatmapSetRequest(1);
api.Queue(req);
- AddUntilStep("wait for api response", () => req.Result != null);
+ AddUntilStep("wait for api response", () => req.Response != null);
TestUpdateableBeatmapBackgroundSprite background = null;
@@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Child = background = new TestUpdateableBeatmapBackgroundSprite
{
RelativeSizeAxes = Axes.Both,
- Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } }
+ Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Response?.ToBeatmapSet(rulesets) } }
};
});
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 696f930467..cd56cb51ae 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -4,6 +4,7 @@
+
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
index bc32a12ab7..f9c553cb3f 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Tests.Components
private void success(APIBeatmap apiBeatmap)
{
- var beatmap = apiBeatmap.ToBeatmap(rulesets);
+ var beatmap = apiBeatmap.ToBeatmapInfo(rulesets);
Add(new TournamentBeatmapPanel(beatmap)
{
Anchor = Anchor.Centre,
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
index 47e7ed9b61..27eb55a9fb 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Tournament.Tests.Components
private FillFlowContainer fillFlow;
- private BeatmapInfo beatmap;
+ private BeatmapInfo beatmapInfo;
[BackgroundDependencyLoader]
private void load()
@@ -44,12 +44,12 @@ namespace osu.Game.Tournament.Tests.Components
private void success(APIBeatmap apiBeatmap)
{
- beatmap = apiBeatmap.ToBeatmap(rulesets);
+ beatmapInfo = apiBeatmap.ToBeatmapInfo(rulesets);
var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods;
foreach (var mod in mods)
{
- fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym)
+ fillFlow.Add(new TournamentBeatmapPanel(beatmapInfo, mod.Acronym)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs
index 6080f7b636..13888699ef 100644
--- a/osu.Game.Tournament/Components/SongBar.cs
+++ b/osu.Game.Tournament/Components/SongBar.cs
@@ -21,22 +21,22 @@ namespace osu.Game.Tournament.Components
{
public class SongBar : CompositeDrawable
{
- private BeatmapInfo beatmap;
+ private BeatmapInfo beatmapInfo;
public const float HEIGHT = 145 / 2f;
[Resolved]
private IBindable ruleset { get; set; }
- public BeatmapInfo Beatmap
+ public BeatmapInfo BeatmapInfo
{
- get => beatmap;
+ get => beatmapInfo;
set
{
- if (beatmap == value)
+ if (beatmapInfo == value)
return;
- beatmap = value;
+ beatmapInfo = value;
update();
}
}
@@ -95,18 +95,18 @@ namespace osu.Game.Tournament.Components
private void update()
{
- if (beatmap == null)
+ if (beatmapInfo == null)
{
flow.Clear();
return;
}
- var bpm = beatmap.BeatmapSet.OnlineInfo.BPM;
- var length = beatmap.Length;
+ var bpm = beatmapInfo.BeatmapSet.OnlineInfo.BPM;
+ var length = beatmapInfo.Length;
string hardRockExtra = "";
string srExtra = "";
- var ar = beatmap.BaseDifficulty.ApproachRate;
+ var ar = beatmapInfo.BaseDifficulty.ApproachRate;
if ((mods & LegacyMods.HardRock) > 0)
{
@@ -117,7 +117,7 @@ namespace osu.Game.Tournament.Components
if ((mods & LegacyMods.DoubleTime) > 0)
{
// temporary local calculation (taken from OsuDifficultyCalculator)
- double preempt = (int)BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / 1.5;
+ double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(ar, 1800, 1200, 450) / 1.5;
ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5);
bpm *= 1.5f;
@@ -132,9 +132,9 @@ namespace osu.Game.Tournament.Components
default:
stats = new (string heading, string content)[]
{
- ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
+ ("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
("AR", $"{ar:0.#}{hardRockExtra}"),
- ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
+ ("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
};
break;
@@ -142,15 +142,15 @@ namespace osu.Game.Tournament.Components
case 3:
stats = new (string heading, string content)[]
{
- ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
- ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}")
+ ("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"),
+ ("HP", $"{beatmapInfo.BaseDifficulty.DrainRate:0.#}{hardRockExtra}")
};
break;
case 2:
stats = new (string heading, string content)[]
{
- ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
+ ("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"),
("AR", $"{ar:0.#}"),
};
break;
@@ -186,7 +186,7 @@ namespace osu.Game.Tournament.Components
Children = new Drawable[]
{
new DiffPiece(stats),
- new DiffPiece(("Star Rating", $"{beatmap.StarDifficulty:0.#}{srExtra}"))
+ new DiffPiece(("Star Rating", $"{beatmapInfo.StarDifficulty:0.#}{srExtra}"))
}
},
new FillFlowContainer
@@ -229,7 +229,7 @@ namespace osu.Game.Tournament.Components
}
}
},
- new TournamentBeatmapPanel(beatmap)
+ new TournamentBeatmapPanel(beatmapInfo)
{
RelativeSizeAxes = Axes.X,
Width = 0.5f,
diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
index e6d73c6e83..0e5a66e7fe 100644
--- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
+++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Components
{
public class TournamentBeatmapPanel : CompositeDrawable
{
- public readonly BeatmapInfo Beatmap;
+ public readonly BeatmapInfo BeatmapInfo;
private readonly string mod;
private const float horizontal_padding = 10;
@@ -32,11 +32,11 @@ namespace osu.Game.Tournament.Components
private readonly Bindable currentMatch = new Bindable();
private Box flash;
- public TournamentBeatmapPanel(BeatmapInfo beatmap, string mod = null)
+ public TournamentBeatmapPanel(BeatmapInfo beatmapInfo, string mod = null)
{
- if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
+ if (beatmapInfo == null) throw new ArgumentNullException(nameof(beatmapInfo));
- Beatmap = beatmap;
+ BeatmapInfo = beatmapInfo;
this.mod = mod;
Width = 400;
Height = HEIGHT;
@@ -61,7 +61,7 @@ namespace osu.Game.Tournament.Components
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.5f),
- BeatmapSet = Beatmap.BeatmapSet,
+ BeatmapSet = BeatmapInfo.BeatmapSet,
},
new FillFlowContainer
{
@@ -75,8 +75,8 @@ namespace osu.Game.Tournament.Components
new TournamentSpriteText
{
Text = new RomanisableString(
- $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}",
- $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}"),
+ $"{BeatmapInfo.Metadata.ArtistUnicode ?? BeatmapInfo.Metadata.Artist} - {BeatmapInfo.Metadata.TitleUnicode ?? BeatmapInfo.Metadata.Title}",
+ $"{BeatmapInfo.Metadata.Artist} - {BeatmapInfo.Metadata.Title}"),
Font = OsuFont.Torus.With(weight: FontWeight.Bold),
},
new FillFlowContainer
@@ -93,7 +93,7 @@ namespace osu.Game.Tournament.Components
},
new TournamentSpriteText
{
- Text = Beatmap.Metadata.AuthorString,
+ Text = BeatmapInfo.Metadata.AuthorString,
Padding = new MarginPadding { Right = 20 },
Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14)
},
@@ -105,7 +105,7 @@ namespace osu.Game.Tournament.Components
},
new TournamentSpriteText
{
- Text = Beatmap.Version,
+ Text = BeatmapInfo.Version,
Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14)
},
}
@@ -149,7 +149,7 @@ namespace osu.Game.Tournament.Components
private void updateState()
{
- var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap.OnlineBeatmapID);
+ var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == BeatmapInfo.OnlineBeatmapID);
bool doFlash = found != choice;
choice = found;
diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs
index f538d4a7d9..7010a30eb7 100644
--- a/osu.Game.Tournament/IPC/FileBasedIPC.cs
+++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs
@@ -94,7 +94,7 @@ namespace osu.Game.Tournament.IPC
else
{
beatmapLookupRequest = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId });
- beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets);
+ beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmapInfo(Rulesets);
API.Queue(beatmapLookupRequest);
}
}
diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
index 50498304ca..b94b164116 100644
--- a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
+++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Tournament.Screens
private void beatmapChanged(ValueChangedEvent beatmap)
{
SongBar.FadeInFromZero(300, Easing.OutQuint);
- SongBar.Beatmap = beatmap.NewValue;
+ SongBar.BeatmapInfo = beatmap.NewValue;
}
}
}
diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
index 27ad6650d1..6e4fc8fe1a 100644
--- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs
@@ -238,7 +238,7 @@ namespace osu.Game.Tournament.Screens.Editors
req.Success += res =>
{
- Model.BeatmapInfo = res.ToBeatmap(rulesets);
+ Model.BeatmapInfo = res.ToBeatmapInfo(rulesets);
updatePanel();
};
diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
index 6418bf97da..b64a3993e6 100644
--- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
+++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs
@@ -246,7 +246,7 @@ namespace osu.Game.Tournament.Screens.Editors
req.Success += res =>
{
- Model.BeatmapInfo = res.ToBeatmap(rulesets);
+ Model.BeatmapInfo = res.ToBeatmapInfo(rulesets);
updatePanel();
};
diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
index d4292c5492..1e3c550323 100644
--- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
+++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs
@@ -147,11 +147,11 @@ namespace osu.Game.Tournament.Screens.MapPool
if (map != null)
{
- if (e.Button == MouseButton.Left && map.Beatmap.OnlineBeatmapID != null)
- addForBeatmap(map.Beatmap.OnlineBeatmapID.Value);
+ if (e.Button == MouseButton.Left && map.BeatmapInfo.OnlineBeatmapID != null)
+ addForBeatmap(map.BeatmapInfo.OnlineBeatmapID.Value);
else
{
- var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap.OnlineBeatmapID);
+ var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.BeatmapInfo.OnlineBeatmapID);
if (existing != null)
{
diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs
index 531da00faf..bdf7269c83 100644
--- a/osu.Game.Tournament/TournamentGameBase.cs
+++ b/osu.Game.Tournament/TournamentGameBase.cs
@@ -182,7 +182,7 @@ namespace osu.Game.Tournament
{
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
API.Perform(req);
- b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore);
+ b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore);
addedInfo = true;
}
@@ -203,7 +203,7 @@ namespace osu.Game.Tournament
{
var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID });
req.Perform(API);
- b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore);
+ b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore);
addedInfo = true;
}
diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs
new file mode 100644
index 0000000000..ee48bdd7d9
--- /dev/null
+++ b/osu.Game/Audio/Effects/AudioFilter.cs
@@ -0,0 +1,137 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Diagnostics;
+using ManagedBass.Fx;
+using osu.Framework.Audio.Mixing;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+
+namespace osu.Game.Audio.Effects
+{
+ public class AudioFilter : Component, ITransformableFilter
+ {
+ ///
+ /// The maximum cutoff frequency that can be used with a low-pass filter.
+ /// This is equal to nyquist - 1hz.
+ ///
+ public const int MAX_LOWPASS_CUTOFF = 22049; // nyquist - 1hz
+
+ private readonly AudioMixer mixer;
+ private readonly BQFParameters filter;
+ private readonly BQFType type;
+
+ ///
+ /// The current cutoff of this filter.
+ ///
+ public BindableNumber Cutoff { get; }
+
+ ///
+ /// A Component that implements a BASS FX BiQuad Filter Effect.
+ ///
+ /// The mixer this effect should be applied to.
+ /// The type of filter (e.g. LowPass, HighPass, etc)
+ public AudioFilter(AudioMixer mixer, BQFType type = BQFType.LowPass)
+ {
+ this.mixer = mixer;
+ this.type = type;
+
+ int initialCutoff;
+
+ switch (type)
+ {
+ case BQFType.HighPass:
+ initialCutoff = 1;
+ break;
+
+ case BQFType.LowPass:
+ initialCutoff = MAX_LOWPASS_CUTOFF;
+ break;
+
+ default:
+ initialCutoff = 500; // A default that should ensure audio remains audible for other filters.
+ break;
+ }
+
+ Cutoff = new BindableNumber(initialCutoff)
+ {
+ MinValue = 1,
+ MaxValue = MAX_LOWPASS_CUTOFF
+ };
+
+ filter = new BQFParameters
+ {
+ lFilter = type,
+ fCenter = initialCutoff,
+ fBandwidth = 0,
+ fQ = 0.7f // This allows fCenter to go up to 22049hz (nyquist - 1hz) without overflowing and causing weird filter behaviour (see: https://www.un4seen.com/forum/?topic=19542.0)
+ };
+
+ // Don't start attached if this is low-pass or high-pass filter (as they have special auto-attach/detach logic)
+ if (type != BQFType.LowPass && type != BQFType.HighPass)
+ attachFilter();
+
+ Cutoff.ValueChanged += updateFilter;
+ }
+
+ private void attachFilter()
+ {
+ Debug.Assert(!mixer.Effects.Contains(filter));
+ mixer.Effects.Add(filter);
+ }
+
+ private void detachFilter()
+ {
+ Debug.Assert(mixer.Effects.Contains(filter));
+ mixer.Effects.Remove(filter);
+ }
+
+ private void updateFilter(ValueChangedEvent cutoff)
+ {
+ // Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz.
+ if (type == BQFType.LowPass)
+ {
+ if (cutoff.NewValue >= MAX_LOWPASS_CUTOFF)
+ {
+ detachFilter();
+ return;
+ }
+
+ if (cutoff.OldValue >= MAX_LOWPASS_CUTOFF && cutoff.NewValue < MAX_LOWPASS_CUTOFF)
+ attachFilter();
+ }
+
+ // Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz.
+ if (type == BQFType.HighPass)
+ {
+ if (cutoff.NewValue <= 1)
+ {
+ detachFilter();
+ return;
+ }
+
+ if (cutoff.OldValue <= 1 && cutoff.NewValue > 1)
+ attachFilter();
+ }
+
+ var filterIndex = mixer.Effects.IndexOf(filter);
+ if (filterIndex < 0) return;
+
+ if (mixer.Effects[filterIndex] is BQFParameters existingFilter)
+ {
+ existingFilter.fCenter = cutoff.NewValue;
+
+ // required to update effect with new parameters.
+ mixer.Effects[filterIndex] = existingFilter;
+ }
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (mixer.Effects.Contains(filter))
+ detachFilter();
+ }
+ }
+}
diff --git a/osu.Game/Audio/Effects/ITransformableFilter.cs b/osu.Game/Audio/Effects/ITransformableFilter.cs
new file mode 100644
index 0000000000..e4de4cf8ff
--- /dev/null
+++ b/osu.Game/Audio/Effects/ITransformableFilter.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 osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Transforms;
+
+namespace osu.Game.Audio.Effects
+{
+ public interface ITransformableFilter
+ {
+ ///
+ /// The filter cutoff.
+ ///
+ BindableNumber Cutoff { get; }
+ }
+
+ public static class FilterableAudioComponentExtensions
+ {
+ ///
+ /// Smoothly adjusts filter cutoff over time.
+ ///
+ /// A to which further transforms can be added.
+ public static TransformSequence CutoffTo(this T component, int newCutoff, double duration = 0, Easing easing = Easing.None)
+ where T : class, ITransformableFilter, IDrawable
+ => component.CutoffTo(newCutoff, duration, new DefaultEasingFunction(easing));
+
+ ///
+ /// Smoothly adjusts filter cutoff over time.
+ ///
+ /// A to which further transforms can be added.
+ public static TransformSequence CutoffTo(this TransformSequence sequence, int newCutoff, double duration = 0, Easing easing = Easing.None)
+ where T : class, ITransformableFilter, IDrawable
+ => sequence.CutoffTo(newCutoff, duration, new DefaultEasingFunction(easing));
+
+ ///
+ /// Smoothly adjusts filter cutoff over time.
+ ///
+ /// A to which further transforms can be added.
+ public static TransformSequence CutoffTo(this T component, int newCutoff, double duration, TEasing easing)
+ where T : class, ITransformableFilter, IDrawable
+ where TEasing : IEasingFunction
+ => component.TransformBindableTo(component.Cutoff, newCutoff, duration, easing);
+
+ ///
+ /// Smoothly adjusts filter cutoff over time.
+ ///
+ /// A to which further transforms can be added.
+ public static TransformSequence CutoffTo(this TransformSequence sequence, int newCutoff, double duration, TEasing easing)
+ where T : class, ITransformableFilter, IDrawable
+ where TEasing : IEasingFunction
+ => sequence.Append(o => o.TransformBindableTo(o.Cutoff, newCutoff, duration, easing));
+ }
+}
diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs
index e5b6a4bc44..b2211e26cf 100644
--- a/osu.Game/Beatmaps/Beatmap.cs
+++ b/osu.Game/Beatmaps/Beatmap.cs
@@ -18,17 +18,48 @@ namespace osu.Game.Beatmaps
public class Beatmap : IBeatmap
where T : HitObject
{
- public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo
+ private BeatmapDifficulty difficulty = new BeatmapDifficulty();
+
+ public BeatmapDifficulty Difficulty
{
- Metadata = new BeatmapMetadata
+ get => difficulty;
+ set
{
- Artist = @"Unknown",
- Title = @"Unknown",
- AuthorString = @"Unknown Creator",
- },
- Version = @"Normal",
- BaseDifficulty = new BeatmapDifficulty()
- };
+ difficulty = value;
+
+ if (beatmapInfo != null)
+ beatmapInfo.BaseDifficulty = difficulty.Clone();
+ }
+ }
+
+ private BeatmapInfo beatmapInfo;
+
+ public BeatmapInfo BeatmapInfo
+ {
+ get => beatmapInfo;
+ set
+ {
+ beatmapInfo = value;
+
+ if (beatmapInfo?.BaseDifficulty != null)
+ Difficulty = beatmapInfo.BaseDifficulty.Clone();
+ }
+ }
+
+ public Beatmap()
+ {
+ beatmapInfo = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Artist = @"Unknown",
+ Title = @"Unknown",
+ AuthorString = @"Unknown Creator",
+ },
+ Version = @"Normal",
+ BaseDifficulty = Difficulty,
+ };
+ }
[JsonIgnore]
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs
index 1844b193f2..dfd21469fa 100644
--- a/osu.Game/Beatmaps/BeatmapDifficulty.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs
@@ -1,11 +1,12 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using osu.Game.Database;
namespace osu.Game.Beatmaps
{
- public class BeatmapDifficulty : IHasPrimaryKey
+ public class BeatmapDifficulty : IHasPrimaryKey, IBeatmapDifficultyInfo
{
///
/// The default value used for all difficulty settings except and .
@@ -20,6 +21,15 @@ namespace osu.Game.Beatmaps
private float? approachRate;
+ public BeatmapDifficulty()
+ {
+ }
+
+ public BeatmapDifficulty(IBeatmapDifficultyInfo source)
+ {
+ CopyFrom(source);
+ }
+
public float ApproachRate
{
get => approachRate ?? OverallDifficulty;
@@ -34,62 +44,31 @@ namespace osu.Game.Beatmaps
///
public BeatmapDifficulty Clone()
{
- var diff = new BeatmapDifficulty();
+ var diff = (BeatmapDifficulty)Activator.CreateInstance(GetType());
CopyTo(diff);
return diff;
}
- public void CopyTo(BeatmapDifficulty difficulty)
+ public virtual void CopyFrom(IBeatmapDifficultyInfo other)
{
- difficulty.ApproachRate = ApproachRate;
- difficulty.DrainRate = DrainRate;
- difficulty.CircleSize = CircleSize;
- difficulty.OverallDifficulty = OverallDifficulty;
+ ApproachRate = other.ApproachRate;
+ DrainRate = other.DrainRate;
+ CircleSize = other.CircleSize;
+ OverallDifficulty = other.OverallDifficulty;
- difficulty.SliderMultiplier = SliderMultiplier;
- difficulty.SliderTickRate = SliderTickRate;
+ SliderMultiplier = other.SliderMultiplier;
+ SliderTickRate = other.SliderTickRate;
}
- ///
- /// Maps a difficulty value [0, 10] to a two-piece linear range of values.
- ///
- /// The difficulty value to be mapped.
- /// Minimum of the resulting range which will be achieved by a difficulty value of 0.
- /// Midpoint of the resulting range which will be achieved by a difficulty value of 5.
- /// Maximum of the resulting range which will be achieved by a difficulty value of 10.
- /// Value to which the difficulty value maps in the specified range.
- public static double DifficultyRange(double difficulty, double min, double mid, double max)
+ public virtual void CopyTo(BeatmapDifficulty other)
{
- if (difficulty > 5)
- return mid + (max - mid) * (difficulty - 5) / 5;
- if (difficulty < 5)
- return mid - (mid - min) * (5 - difficulty) / 5;
+ other.ApproachRate = ApproachRate;
+ other.DrainRate = DrainRate;
+ other.CircleSize = CircleSize;
+ other.OverallDifficulty = OverallDifficulty;
- return mid;
+ other.SliderMultiplier = SliderMultiplier;
+ other.SliderTickRate = SliderTickRate;
}
-
- ///
- /// Maps a difficulty value [0, 10] to a two-piece linear range of values.
- ///
- /// The difficulty value to be mapped.
- /// The values that define the two linear ranges.
- ///
- /// -
- /// od0
- /// Minimum of the resulting range which will be achieved by a difficulty value of 0.
- ///
- /// -
- /// od5
- /// Midpoint of the resulting range which will be achieved by a difficulty value of 5.
- ///
- /// -
- /// od10
- /// Maximum of the resulting range which will be achieved by a difficulty value of 10.
- ///
- ///
- ///
- /// Value to which the difficulty value maps in the specified range.
- public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range)
- => DifficultyRange(difficulty, range.od0, range.od5, range.od10);
}
}
diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
index 0aa6a6dd0b..3777365088 100644
--- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
+++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs
@@ -17,6 +17,7 @@ using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
@@ -147,6 +148,14 @@ namespace osu.Game.Beatmaps
}, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
}
+ public Task> GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default)
+ {
+ return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods),
+ token,
+ TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously,
+ updateScheduler);
+ }
+
///
/// Retrieves the that describes a star rating.
///
@@ -242,7 +251,7 @@ namespace osu.Game.Beatmaps
{
// GetDifficultyAsync will fall back to existing data from BeatmapInfo if not locally available
// (contrary to GetAsync)
- GetDifficultyAsync(bindable.Beatmap, rulesetInfo, mods, cancellationToken)
+ GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken)
.ContinueWith(t =>
{
// We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events.
@@ -262,7 +271,7 @@ namespace osu.Game.Beatmaps
private StarDifficulty computeDifficulty(in DifficultyCacheLookup key)
{
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
- var beatmapInfo = key.Beatmap;
+ var beatmapInfo = key.BeatmapInfo;
var rulesetInfo = key.Ruleset;
try
@@ -270,7 +279,7 @@ namespace osu.Game.Beatmaps
var ruleset = rulesetInfo.CreateInstance();
Debug.Assert(ruleset != null);
- var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.Beatmap));
+ var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo));
var attributes = calculator.Calculate(key.OrderedMods);
return new StarDifficulty(attributes);
@@ -300,21 +309,21 @@ namespace osu.Game.Beatmaps
public readonly struct DifficultyCacheLookup : IEquatable
{
- public readonly BeatmapInfo Beatmap;
+ public readonly BeatmapInfo BeatmapInfo;
public readonly RulesetInfo Ruleset;
public readonly Mod[] OrderedMods;
- public DifficultyCacheLookup([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, IEnumerable mods)
+ public DifficultyCacheLookup([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, IEnumerable mods)
{
- Beatmap = beatmap;
+ BeatmapInfo = beatmapInfo;
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
- Ruleset = ruleset ?? Beatmap.Ruleset;
+ Ruleset = ruleset ?? BeatmapInfo.Ruleset;
OrderedMods = mods?.OrderBy(m => m.Acronym).Select(mod => mod.DeepClone()).ToArray() ?? Array.Empty();
}
public bool Equals(DifficultyCacheLookup other)
- => Beatmap.ID == other.Beatmap.ID
+ => BeatmapInfo.ID == other.BeatmapInfo.ID
&& Ruleset.ID == other.Ruleset.ID
&& OrderedMods.SequenceEqual(other.OrderedMods);
@@ -322,7 +331,7 @@ namespace osu.Game.Beatmaps
{
var hashCode = new HashCode();
- hashCode.Add(Beatmap.ID);
+ hashCode.Add(BeatmapInfo.ID);
hashCode.Add(Ruleset.ID);
foreach (var mod in OrderedMods)
@@ -334,12 +343,12 @@ namespace osu.Game.Beatmaps
private class BindableStarDifficulty : Bindable
{
- public readonly BeatmapInfo Beatmap;
+ public readonly BeatmapInfo BeatmapInfo;
public readonly CancellationToken CancellationToken;
- public BindableStarDifficulty(BeatmapInfo beatmap, CancellationToken cancellationToken)
+ public BindableStarDifficulty(BeatmapInfo beatmapInfo, CancellationToken cancellationToken)
{
- Beatmap = beatmap;
+ BeatmapInfo = beatmapInfo;
CancellationToken = cancellationToken;
}
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 8cb5da8083..ac5b5d7a8a 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -7,7 +7,6 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using Newtonsoft.Json;
-using osu.Framework.Localisation;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Rulesets;
@@ -17,7 +16,7 @@ namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
[Serializable]
- public class BeatmapInfo : IEquatable, IHasPrimaryKey
+ public class BeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo
{
public int ID { get; set; }
@@ -152,18 +151,7 @@ namespace osu.Game.Beatmaps
[JsonIgnore]
public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty);
- public string[] SearchableTerms => new[]
- {
- Version
- }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
-
- public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {versionString}".Trim();
-
- public RomanisableString ToRomanisableString()
- {
- var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null);
- return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim());
- }
+ public override string ToString() => this.GetDisplayTitle();
public bool Equals(BeatmapInfo other)
{
@@ -187,5 +175,22 @@ namespace osu.Game.Beatmaps
/// Returns a shallow-clone of this .
///
public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone();
+
+ #region Implementation of IHasOnlineID
+
+ public int? OnlineID => OnlineBeatmapID;
+
+ #endregion
+
+ #region Implementation of IBeatmapInfo
+
+ string IBeatmapInfo.DifficultyName => Version;
+ IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
+ IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty;
+ IBeatmapSetInfo IBeatmapInfo.BeatmapSet => BeatmapSet;
+ IRulesetInfo IBeatmapInfo.Ruleset => Ruleset;
+ double IBeatmapInfo.StarRating => StarDifficulty;
+
+ #endregion
}
}
diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs
new file mode 100644
index 0000000000..eba19ac1a1
--- /dev/null
+++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs
@@ -0,0 +1,38 @@
+// 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.Localisation;
+
+namespace osu.Game.Beatmaps
+{
+ public static class BeatmapInfoExtensions
+ {
+ ///
+ /// A user-presentable display title representing this beatmap.
+ ///
+ public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{getClosestMetadata(beatmapInfo)} {getVersionString(beatmapInfo)}".Trim();
+
+ ///
+ /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
+ ///
+ public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo)
+ {
+ var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable();
+ var versionString = getVersionString(beatmapInfo);
+
+ return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim());
+ }
+
+ public static string[] GetSearchableTerms(this IBeatmapInfo beatmapInfo) => new[]
+ {
+ beatmapInfo.DifficultyName
+ }.Concat(getClosestMetadata(beatmapInfo).GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
+
+ private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
+
+ // temporary helper methods until we figure which metadata should be where.
+ private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) =>
+ beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet?.Metadata ?? new BeatmapMetadata();
+ }
+}
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 2f80633279..240db22c00 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps
private readonly BeatmapModelDownloader beatmapModelDownloader;
private readonly WorkingBeatmapCache workingBeatmapCache;
- private readonly BeatmapOnlineLookupQueue onlineBetamapLookupQueue;
+ private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue;
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null,
WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false)
@@ -45,11 +45,12 @@ namespace osu.Game.Beatmaps
workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, resources, new FileStore(contextFactory, storage).Store, defaultBeatmap, host);
workingBeatmapCache.BeatmapManager = beatmapModelManager;
+ beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache;
if (performOnlineLookups)
{
- onlineBetamapLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
- beatmapModelManager.OnlineLookupQueue = onlineBetamapLookupQueue;
+ onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
+ beatmapModelManager.OnlineLookupQueue = onlineBeatmapLookupQueue;
}
}
@@ -90,8 +91,9 @@ namespace osu.Game.Beatmaps
}
};
- var working = beatmapModelManager.Import(set).Result;
- return GetWorkingBeatmap(working.Beatmaps.First());
+ var imported = beatmapModelManager.Import(set).Result.Value;
+
+ return GetWorkingBeatmap(imported.Beatmaps.First());
}
#region Delegation to BeatmapModelManager (methods which previously existed locally).
@@ -177,19 +179,19 @@ namespace osu.Game.Beatmaps
///
/// Fired when the user requests to view the resulting import.
///
- public Action> PresentImport { set => beatmapModelManager.PresentImport = value; }
+ public Action>> PresentImport { set => beatmapModelManager.PostImport = value; }
///
/// Delete a beatmap difficulty.
///
- /// The beatmap difficulty to hide.
- public void Hide(BeatmapInfo beatmap) => beatmapModelManager.Hide(beatmap);
+ /// The beatmap difficulty to hide.
+ public void Hide(BeatmapInfo beatmapInfo) => beatmapModelManager.Hide(beatmapInfo);
///
/// Restore a beatmap difficulty.
///
- /// The beatmap difficulty to restore.
- public void Restore(BeatmapInfo beatmap) => beatmapModelManager.Restore(beatmap);
+ /// The beatmap difficulty to restore.
+ public void Restore(BeatmapInfo beatmapInfo) => beatmapModelManager.Restore(beatmapInfo);
#endregion
@@ -276,22 +278,22 @@ namespace osu.Game.Beatmaps
return beatmapModelManager.Import(tasks);
}
- public Task> Import(ProgressNotification notification, params ImportTask[] tasks)
+ public Task>> Import(ProgressNotification notification, params ImportTask[] tasks)
{
return beatmapModelManager.Import(notification, tasks);
}
- public Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{
return beatmapModelManager.Import(task, lowPriority, cancellationToken);
}
- public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
{
return beatmapModelManager.Import(archive, lowPriority, cancellationToken);
}
- public Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken);
}
@@ -304,6 +306,9 @@ namespace osu.Game.Beatmaps
public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo importedBeatmap) => workingBeatmapCache.GetWorkingBeatmap(importedBeatmap);
+ void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo);
+ void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo);
+
#endregion
#region Implementation of IModelFileManager
@@ -329,7 +334,7 @@ namespace osu.Game.Beatmaps
public void Dispose()
{
- onlineBetamapLookupQueue?.Dispose();
+ onlineBeatmapLookupQueue?.Dispose();
}
#endregion
diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs
index 713f80d1fe..711533e118 100644
--- a/osu.Game/Beatmaps/BeatmapMetadata.cs
+++ b/osu.Game/Beatmaps/BeatmapMetadata.cs
@@ -4,9 +4,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
-using System.Linq;
using Newtonsoft.Json;
-using osu.Framework.Localisation;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Users;
@@ -15,7 +13,7 @@ namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
[Serializable]
- public class BeatmapMetadata : IEquatable, IHasPrimaryKey
+ public class BeatmapMetadata : IEquatable, IHasPrimaryKey, IBeatmapMetadataInfo
{
public int ID { get; set; }
@@ -83,50 +81,13 @@ namespace osu.Game.Beatmaps
public int PreviewTime { get; set; }
public string AudioFile { get; set; }
+
public string BackgroundFile { get; set; }
- public override string ToString()
- {
- string author = Author == null ? string.Empty : $"({Author})";
- return $"{Artist} - {Title} {author}".Trim();
- }
+ public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other);
- public RomanisableString ToRomanisableString()
- {
- string author = Author == null ? string.Empty : $"({Author})";
- var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode;
- var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode;
+ public override string ToString() => this.GetDisplayTitle();
- return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim());
- }
-
- [JsonIgnore]
- public string[] SearchableTerms => new[]
- {
- Author?.Username,
- Artist,
- ArtistUnicode,
- Title,
- TitleUnicode,
- Source,
- Tags
- }.Where(s => !string.IsNullOrEmpty(s)).ToArray();
-
- public bool Equals(BeatmapMetadata other)
- {
- if (other == null)
- return false;
-
- return Title == other.Title
- && TitleUnicode == other.TitleUnicode
- && Artist == other.Artist
- && ArtistUnicode == other.ArtistUnicode
- && AuthorString == other.AuthorString
- && Source == other.Source
- && Tags == other.Tags
- && PreviewTime == other.PreviewTime
- && AudioFile == other.AudioFile
- && BackgroundFile == other.BackgroundFile;
- }
+ string IBeatmapMetadataInfo.Author => AuthorString;
}
}
diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs
new file mode 100644
index 0000000000..ee946eeeec
--- /dev/null
+++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Localisation;
+
+namespace osu.Game.Beatmaps
+{
+ public static class BeatmapMetadataInfoExtensions
+ {
+ ///
+ /// An array of all searchable terms provided in contained metadata.
+ ///
+ public static string[] GetSearchableTerms(this IBeatmapMetadataInfo metadataInfo) => new[]
+ {
+ metadataInfo.Author,
+ metadataInfo.Artist,
+ metadataInfo.ArtistUnicode,
+ metadataInfo.Title,
+ metadataInfo.TitleUnicode,
+ metadataInfo.Source,
+ metadataInfo.Tags
+ }.Where(s => !string.IsNullOrEmpty(s)).ToArray();
+
+ ///
+ /// A user-presentable display title representing this metadata.
+ ///
+ public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo)
+ {
+ string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})";
+ return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim();
+ }
+
+ ///
+ /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
+ ///
+ public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo)
+ {
+ string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})";
+ var artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode;
+ var titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode;
+
+ return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim());
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs
index 0beddc1e9b..9c0fc5ef8a 100644
--- a/osu.Game/Beatmaps/BeatmapModelManager.cs
+++ b/osu.Game/Beatmaps/BeatmapModelManager.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps
/// Handles ef-core storage of beatmaps.
///
[ExcludeFromDynamicCompile]
- public class BeatmapModelManager : ArchiveModelManager
+ public class BeatmapModelManager : ArchiveModelManager, IBeatmapModelManager
{
///
/// Fired when a single difficulty has been hidden.
@@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps
///
/// The game working beatmap cache, used to invalidate entries on changes.
///
- public WorkingBeatmapCache WorkingBeatmapCache { private get; set; }
+ public IWorkingBeatmapCache WorkingBeatmapCache { private get; set; }
private readonly Bindable> beatmapRestored = new Bindable>();
@@ -173,24 +173,24 @@ namespace osu.Game.Beatmaps
///
/// Delete a beatmap difficulty.
///
- /// The beatmap difficulty to hide.
- public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap);
+ /// The beatmap difficulty to hide.
+ public void Hide(BeatmapInfo beatmapInfo) => beatmaps.Hide(beatmapInfo);
///
/// Restore a beatmap difficulty.
///
- /// The beatmap difficulty to restore.
- public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
+ /// The beatmap difficulty to restore.
+ public void Restore(BeatmapInfo beatmapInfo) => beatmaps.Restore(beatmapInfo);
///
/// Saves an file against a given .
///
- /// The to save the content against. The file referenced by will be replaced.
+ /// The to save the content against. The file referenced by will be replaced.
/// The content to write.
/// The beatmap content to write, null if to be omitted.
- public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null)
+ public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin beatmapSkin = null)
{
- var setInfo = info.BeatmapSet;
+ var setInfo = beatmapInfo.BeatmapSet;
using (var stream = new MemoryStream())
{
@@ -201,7 +201,9 @@ namespace osu.Game.Beatmaps
using (ContextFactory.GetForWrite())
{
- var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID);
+ beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID);
+ beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty);
+
var metadata = beatmapInfo.Metadata ?? setInfo.Metadata;
// grab the original file (or create a new one if not found).
@@ -219,7 +221,7 @@ namespace osu.Game.Beatmaps
}
}
- WorkingBeatmapCache?.Invalidate(info);
+ WorkingBeatmapCache?.Invalidate(beatmapInfo);
}
///
diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs
index 55164e2442..1fe120557d 100644
--- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs
+++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs
@@ -58,18 +58,18 @@ namespace osu.Game.Beatmaps
}
// todo: expose this when we need to do individual difficulty lookups.
- protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken)
- => Task.Factory.StartNew(() => lookup(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
+ protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmapInfo, CancellationToken cancellationToken)
+ => Task.Factory.StartNew(() => lookup(beatmapSet, beatmapInfo), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler);
- private void lookup(BeatmapSetInfo set, BeatmapInfo beatmap)
+ private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo)
{
- if (checkLocalCache(set, beatmap))
+ if (checkLocalCache(set, beatmapInfo))
return;
if (api?.State.Value != APIState.Online)
return;
- var req = new GetBeatmapRequest(beatmap);
+ var req = new GetBeatmapRequest(beatmapInfo);
req.Failure += fail;
@@ -78,22 +78,22 @@ namespace osu.Game.Beatmaps
// intentionally blocking to limit web request concurrency
api.Perform(req);
- var res = req.Result;
+ var res = req.Response;
if (res != null)
{
- beatmap.Status = res.Status;
- beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
- beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
- beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
+ beatmapInfo.Status = res.Status;
+ beatmapInfo.BeatmapSet.Status = res.BeatmapSet.Status;
+ beatmapInfo.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
+ beatmapInfo.OnlineBeatmapID = res.OnlineBeatmapID;
- if (beatmap.Metadata != null)
- beatmap.Metadata.AuthorID = res.AuthorID;
+ if (beatmapInfo.Metadata != null)
+ beatmapInfo.Metadata.AuthorID = res.AuthorID;
- if (beatmap.BeatmapSet.Metadata != null)
- beatmap.BeatmapSet.Metadata.AuthorID = res.AuthorID;
+ if (beatmapInfo.BeatmapSet.Metadata != null)
+ beatmapInfo.BeatmapSet.Metadata.AuthorID = res.AuthorID;
- logForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
+ logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
}
}
catch (Exception e)
@@ -103,8 +103,8 @@ namespace osu.Game.Beatmaps
void fail(Exception e)
{
- beatmap.OnlineBeatmapID = null;
- logForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})");
+ beatmapInfo.OnlineBeatmapID = null;
+ logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})");
}
}
@@ -149,7 +149,7 @@ namespace osu.Game.Beatmaps
cacheDownloadRequest.PerformAsync();
}
- private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmap)
+ private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo)
{
// download is in progress (or was, and failed).
if (cacheDownloadRequest != null)
@@ -159,9 +159,9 @@ namespace osu.Game.Beatmaps
if (!storage.Exists(cache_database_name))
return false;
- if (string.IsNullOrEmpty(beatmap.MD5Hash)
- && string.IsNullOrEmpty(beatmap.Path)
- && beatmap.OnlineBeatmapID == null)
+ if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
+ && string.IsNullOrEmpty(beatmapInfo.Path)
+ && beatmapInfo.OnlineBeatmapID == null)
return false;
try
@@ -174,9 +174,9 @@ namespace osu.Game.Beatmaps
{
cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path";
- cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmap.MD5Hash));
- cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmap.OnlineBeatmapID ?? (object)DBNull.Value));
- cmd.Parameters.Add(new SqliteParameter("@Path", beatmap.Path));
+ cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash));
+ cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmapInfo.OnlineBeatmapID ?? (object)DBNull.Value));
+ cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path));
using (var reader = cmd.ExecuteReader())
{
@@ -184,18 +184,18 @@ namespace osu.Game.Beatmaps
{
var status = (BeatmapSetOnlineStatus)reader.GetByte(2);
- beatmap.Status = status;
- beatmap.BeatmapSet.Status = status;
- beatmap.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0);
- beatmap.OnlineBeatmapID = reader.GetInt32(1);
+ beatmapInfo.Status = status;
+ beatmapInfo.BeatmapSet.Status = status;
+ beatmapInfo.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0);
+ beatmapInfo.OnlineBeatmapID = reader.GetInt32(1);
- if (beatmap.Metadata != null)
- beatmap.Metadata.AuthorID = reader.GetInt32(3);
+ if (beatmapInfo.Metadata != null)
+ beatmapInfo.Metadata.AuthorID = reader.GetInt32(3);
- if (beatmap.BeatmapSet.Metadata != null)
- beatmap.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3);
+ if (beatmapInfo.BeatmapSet.Metadata != null)
+ beatmapInfo.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3);
- logForModel(set, $"Cached local retrieval for {beatmap}.");
+ logForModel(set, $"Cached local retrieval for {beatmapInfo}.");
return true;
}
}
@@ -204,7 +204,7 @@ namespace osu.Game.Beatmaps
}
catch (Exception ex)
{
- logForModel(set, $"Cached local retrieval for {beatmap} failed with {ex}.");
+ logForModel(set, $"Cached local retrieval for {beatmapInfo} failed with {ex}.");
}
return false;
diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
index 3a55dc1577..ce50463f05 100644
--- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs
@@ -7,7 +7,7 @@ using osu.Game.IO;
namespace osu.Game.Beatmaps
{
- public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey
+ public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage
{
public int ID { get; set; }
@@ -19,5 +19,7 @@ namespace osu.Game.Beatmaps
[Required]
public string Filename { get; set; }
+
+ public IFileInfo File => FileInfo;
}
}
diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs
index 3b1ff4ced0..8b01831b3c 100644
--- a/osu.Game/Beatmaps/BeatmapSetInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs
@@ -12,7 +12,7 @@ using osu.Game.Database;
namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
- public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable
+ public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo
{
public int ID { get; set; }
@@ -61,8 +61,6 @@ namespace osu.Game.Beatmaps
public string Hash { get; set; }
- public string StoryboardFile => Files.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
-
///
/// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null.
/// The path returned is relative to the user file storage.
@@ -90,5 +88,19 @@ namespace osu.Game.Beatmaps
return ReferenceEquals(this, other);
}
+
+ #region Implementation of IHasOnlineID
+
+ public int? OnlineID => OnlineBeatmapSetID;
+
+ #endregion
+
+ #region Implementation of IBeatmapSetInfo
+
+ IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata;
+ IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps;
+ IEnumerable IBeatmapSetInfo.Files => Files;
+
+ #endregion
}
}
diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs
index e3214b7c03..197581db88 100644
--- a/osu.Game/Beatmaps/BeatmapStore.cs
+++ b/osu.Game/Beatmaps/BeatmapStore.cs
@@ -25,40 +25,40 @@ namespace osu.Game.Beatmaps
///
/// Hide a in the database.
///
- /// The beatmap to hide.
+ /// The beatmap to hide.
/// Whether the beatmap's was changed.
- public bool Hide(BeatmapInfo beatmap)
+ public bool Hide(BeatmapInfo beatmapInfo)
{
using (ContextFactory.GetForWrite())
{
- Refresh(ref beatmap, Beatmaps);
+ Refresh(ref beatmapInfo, Beatmaps);
- if (beatmap.Hidden) return false;
+ if (beatmapInfo.Hidden) return false;
- beatmap.Hidden = true;
+ beatmapInfo.Hidden = true;
}
- BeatmapHidden?.Invoke(beatmap);
+ BeatmapHidden?.Invoke(beatmapInfo);
return true;
}
///
/// Restore a previously hidden .
///
- /// The beatmap to restore.
+ /// The beatmap to restore.
/// Whether the beatmap's was changed.
- public bool Restore(BeatmapInfo beatmap)
+ public bool Restore(BeatmapInfo beatmapInfo)
{
using (ContextFactory.GetForWrite())
{
- Refresh(ref beatmap, Beatmaps);
+ Refresh(ref beatmapInfo, Beatmaps);
- if (!beatmap.Hidden) return false;
+ if (!beatmapInfo.Hidden) return false;
- beatmap.Hidden = false;
+ beatmapInfo.Hidden = false;
}
- BeatmapRestored?.Invoke(beatmap);
+ BeatmapRestored?.Invoke(beatmapInfo);
return true;
}
diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs
index ca910e70b8..b1b1e58ab7 100644
--- a/osu.Game/Beatmaps/DifficultyRecommender.cs
+++ b/osu.Game/Beatmaps/DifficultyRecommender.cs
@@ -62,14 +62,14 @@ namespace osu.Game.Beatmaps
if (!recommendedDifficultyMapping.TryGetValue(r, out var recommendation))
continue;
- BeatmapInfo beatmap = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b =>
+ BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b =>
{
var difference = b.StarDifficulty - recommendation;
return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder
}).FirstOrDefault();
- if (beatmap != null)
- return beatmap;
+ if (beatmapInfo != null)
+ return beatmapInfo;
}
return null;
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 0751a777d8..880d70aec2 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps.Drawables
}
[NotNull]
- private readonly BeatmapInfo beatmap;
+ private readonly BeatmapInfo beatmapInfo;
[CanBeNull]
private readonly RulesetInfo ruleset;
@@ -56,26 +56,26 @@ namespace osu.Game.Beatmaps.Drawables
///
/// Creates a new with a given and combination.
///
- /// The beatmap to show the difficulty of.
+ /// The beatmap to show the difficulty of.
/// The ruleset to show the difficulty with.
/// The mods to show the difficulty with.
/// Whether to display a tooltip when hovered.
- public DifficultyIcon([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true)
- : this(beatmap, shouldShowTooltip)
+ public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true)
+ : this(beatmapInfo, shouldShowTooltip)
{
- this.ruleset = ruleset ?? beatmap.Ruleset;
+ this.ruleset = ruleset ?? beatmapInfo.Ruleset;
this.mods = mods ?? Array.Empty();
}
///
/// Creates a new that follows the currently-selected ruleset and mods.
///
- /// The beatmap to show the difficulty of.
+ /// The beatmap to show the difficulty of.
/// Whether to display a tooltip when hovered.
/// Whether to perform difficulty lookup (including calculation if necessary).
- public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
+ public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
{
- this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));
+ this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo));
this.shouldShowTooltip = shouldShowTooltip;
this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup;
@@ -105,7 +105,7 @@ namespace osu.Game.Beatmaps.Drawables
Child = background = new Box
{
RelativeSizeAxes = Axes.Both,
- Colour = colours.ForStarDifficulty(beatmap.StarDifficulty) // Default value that will be re-populated once difficulty calculation completes
+ Colour = colours.ForStarDifficulty(beatmapInfo.StarDifficulty) // Default value that will be re-populated once difficulty calculation completes
},
},
new ConstrainedIconContainer
@@ -114,27 +114,27 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
- Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
+ Icon = (ruleset ?? beatmapInfo.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
},
};
if (performBackgroundDifficultyLookup)
- iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0));
+ iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0));
else
- difficultyBindable.Value = new StarDifficulty(beatmap.StarDifficulty, 0);
+ difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarDifficulty, 0);
difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars));
}
ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip();
- DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null;
+ DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null;
private class DifficultyRetriever : Component
{
public readonly Bindable StarDifficulty = new Bindable();
- private readonly BeatmapInfo beatmap;
+ private readonly BeatmapInfo beatmapInfo;
private readonly RulesetInfo ruleset;
private readonly IReadOnlyList mods;
@@ -143,9 +143,9 @@ namespace osu.Game.Beatmaps.Drawables
[Resolved]
private BeatmapDifficultyCache difficultyCache { get; set; }
- public DifficultyRetriever(BeatmapInfo beatmap, RulesetInfo ruleset, IReadOnlyList mods)
+ public DifficultyRetriever(BeatmapInfo beatmapInfo, RulesetInfo ruleset, IReadOnlyList mods)
{
- this.beatmap = beatmap;
+ this.beatmapInfo = beatmapInfo;
this.ruleset = ruleset;
this.mods = mods;
}
@@ -157,8 +157,8 @@ namespace osu.Game.Beatmaps.Drawables
{
difficultyCancellation = new CancellationTokenSource();
localStarDifficulty = ruleset != null
- ? difficultyCache.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token)
- : difficultyCache.GetBindableDifficulty(beatmap, difficultyCancellation.Token);
+ ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token)
+ : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token);
localStarDifficulty.BindValueChanged(d =>
{
if (d.NewValue is StarDifficulty diff)
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs
index 0329e935bc..d4c9f83a0a 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs
@@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps.Drawables
public void SetContent(DifficultyIconTooltipContent content)
{
- difficultyName.Text = content.Beatmap.Version;
+ difficultyName.Text = content.BeatmapInfo.Version;
starDifficulty.UnbindAll();
starDifficulty.BindTo(content.Difficulty);
@@ -109,12 +109,12 @@ namespace osu.Game.Beatmaps.Drawables
internal class DifficultyIconTooltipContent
{
- public readonly BeatmapInfo Beatmap;
+ public readonly BeatmapInfo BeatmapInfo;
public readonly IBindable Difficulty;
- public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable difficulty)
+ public DifficultyIconTooltipContent(BeatmapInfo beatmapInfo, IBindable difficulty)
{
- Beatmap = beatmap;
+ BeatmapInfo = beatmapInfo;
Difficulty = difficulty;
}
}
diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
index 988968fa42..0d5c48f64d 100644
--- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs
@@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps.Formats
stream.ReadToEnd().DeserializeInto(output);
foreach (var hitObject in output.HitObjects)
- hitObject.ApplyDefaults(output.ControlPointInfo, output.BeatmapInfo.BaseDifficulty);
+ hitObject.ApplyDefaults(output.ControlPointInfo, output.Difficulty);
}
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 4b5eaafa4a..f71b148008 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.Formats
this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList();
foreach (var hitObject in this.beatmap.HitObjects)
- hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty);
+ hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty);
}
protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_');
@@ -276,7 +276,7 @@ namespace osu.Game.Beatmaps.Formats
{
var pair = SplitKeyVal(line);
- var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
+ var difficulty = beatmap.Difficulty;
switch (pair.Key)
{
@@ -444,7 +444,7 @@ namespace osu.Game.Beatmaps.Formats
if (obj != null)
{
- obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
+ obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
beatmap.HitObjects.Add(obj);
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index aef13b8872..74b3c178cd 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -141,17 +141,17 @@ namespace osu.Game.Beatmaps.Formats
{
writer.WriteLine("[Difficulty]");
- writer.WriteLine(FormattableString.Invariant($"HPDrainRate: {beatmap.BeatmapInfo.BaseDifficulty.DrainRate}"));
- writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.BeatmapInfo.BaseDifficulty.CircleSize}"));
- writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}"));
- writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}"));
+ writer.WriteLine(FormattableString.Invariant($"HPDrainRate: {beatmap.Difficulty.DrainRate}"));
+ writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.Difficulty.CircleSize}"));
+ writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.Difficulty.OverallDifficulty}"));
+ writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.Difficulty.ApproachRate}"));
// Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER)
writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1
- ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}")
- : FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}"));
+ ? FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}")
+ : FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}"));
- writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}"));
+ writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.Difficulty.SliderTickRate}"));
}
private void handleEvents(TextWriter writer)
@@ -285,7 +285,7 @@ namespace osu.Game.Beatmaps.Formats
break;
case 3:
- int totalColumns = (int)Math.Max(1, beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
+ int totalColumns = (int)Math.Max(1, beatmap.Difficulty.CircleSize);
position.X = (int)Math.Ceiling(((IHasXPosition)hitObject).X * (512f / totalColumns));
break;
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index 6301c42deb..0f15e28c00 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -176,8 +176,8 @@ namespace osu.Game.Beatmaps.Formats
case "L":
{
var startTime = Parsing.ParseDouble(split[1]);
- var loopCount = Parsing.ParseInt(split[2]);
- timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount);
+ var repeatCount = Parsing.ParseInt(split[2]);
+ timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount - 1));
break;
}
diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs
index f61dd269e1..3f598cd1e5 100644
--- a/osu.Game/Beatmaps/IBeatmap.cs
+++ b/osu.Game/Beatmaps/IBeatmap.cs
@@ -20,6 +20,11 @@ namespace osu.Game.Beatmaps
///
BeatmapMetadata Metadata { get; }
+ ///
+ /// This beatmap's difficulty settings.
+ ///
+ public BeatmapDifficulty Difficulty { get; set; }
+
///
/// The control points in this beatmap.
///
diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs
new file mode 100644
index 0000000000..339364d442
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs
@@ -0,0 +1,90 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// A representation of all top-level difficulty settings for a beatmap.
+ ///
+ public interface IBeatmapDifficultyInfo
+ {
+ ///
+ /// The default value used for all difficulty settings except and .
+ ///
+ const float DEFAULT_DIFFICULTY = 5;
+
+ ///
+ /// The drain rate of the associated beatmap.
+ ///
+ float DrainRate { get; }
+
+ ///
+ /// The circle size of the associated beatmap.
+ ///
+ float CircleSize { get; }
+
+ ///
+ /// The overall difficulty of the associated beatmap.
+ ///
+ float OverallDifficulty { get; }
+
+ ///
+ /// The approach rate of the associated beatmap.
+ ///
+ float ApproachRate { get; }
+
+ ///
+ /// The slider multiplier of the associated beatmap.
+ ///
+ double SliderMultiplier { get; }
+
+ ///
+ /// The slider tick rate of the associated beatmap.
+ ///
+ double SliderTickRate { get; }
+
+ ///
+ /// Maps a difficulty value [0, 10] to a two-piece linear range of values.
+ ///
+ /// The difficulty value to be mapped.
+ /// Minimum of the resulting range which will be achieved by a difficulty value of 0.
+ /// Midpoint of the resulting range which will be achieved by a difficulty value of 5.
+ /// Maximum of the resulting range which will be achieved by a difficulty value of 10.
+ /// Value to which the difficulty value maps in the specified range.
+ static double DifficultyRange(double difficulty, double min, double mid, double max)
+ {
+ if (difficulty > 5)
+ return mid + (max - mid) * (difficulty - 5) / 5;
+ if (difficulty < 5)
+ return mid - (mid - min) * (5 - difficulty) / 5;
+
+ return mid;
+ }
+
+ ///
+ /// Maps a difficulty value [0, 10] to a two-piece linear range of values.
+ ///
+ /// The difficulty value to be mapped.
+ /// The values that define the two linear ranges.
+ ///
+ /// -
+ /// od0
+ /// Minimum of the resulting range which will be achieved by a difficulty value of 0.
+ ///
+ /// -
+ /// od5
+ /// Midpoint of the resulting range which will be achieved by a difficulty value of 5.
+ ///
+ /// -
+ /// od10
+ /// Maximum of the resulting range which will be achieved by a difficulty value of 10.
+ ///
+ ///
+ ///
+ /// Value to which the difficulty value maps in the specified range.
+ static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range)
+ => DifficultyRange(difficulty, range.od0, range.od5, range.od10);
+ }
+}
diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs
new file mode 100644
index 0000000000..3d51c5d4b6
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapInfo.cs
@@ -0,0 +1,66 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Database;
+using osu.Game.Rulesets;
+
+#nullable enable
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// A single beatmap difficulty.
+ ///
+ public interface IBeatmapInfo : IHasOnlineID
+ {
+ ///
+ /// The user-specified name given to this beatmap.
+ ///
+ string DifficultyName { get; }
+
+ ///
+ /// The metadata representing this beatmap. May be shared between multiple beatmaps.
+ ///
+ IBeatmapMetadataInfo? Metadata { get; }
+
+ ///
+ /// The difficulty settings for this beatmap.
+ ///
+ IBeatmapDifficultyInfo Difficulty { get; }
+
+ ///
+ /// The beatmap set this beatmap is part of.
+ ///
+ IBeatmapSetInfo? BeatmapSet { get; }
+
+ ///
+ /// The playable length in milliseconds of this beatmap.
+ ///
+ double Length { get; }
+
+ ///
+ /// The most common BPM of this beatmap.
+ ///
+ double BPM { get; }
+
+ ///
+ /// The SHA-256 hash representing this beatmap's contents.
+ ///
+ string Hash { get; }
+
+ ///
+ /// MD5 is kept for legacy support (matching against replays etc.).
+ ///
+ string MD5Hash { get; }
+
+ ///
+ /// The ruleset this beatmap was made for.
+ ///
+ IRulesetInfo Ruleset { get; }
+
+ ///
+ /// The basic star rating for this beatmap (with no mods applied).
+ ///
+ double StarRating { get; }
+ }
+}
diff --git a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs
new file mode 100644
index 0000000000..55aee7d7bc
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapMetadataInfo.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;
+
+#nullable enable
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// Metadata representing a beatmap. May be shared between multiple beatmap difficulties.
+ ///
+ public interface IBeatmapMetadataInfo : IEquatable
+ {
+ ///
+ /// The romanised title of this beatmap.
+ ///
+ string Title { get; }
+
+ ///
+ /// The unicode title of this beatmap.
+ ///
+ string TitleUnicode { get; }
+
+ ///
+ /// The romanised artist of this beatmap.
+ ///
+ string Artist { get; }
+
+ ///
+ /// The unicode artist of this beatmap.
+ ///
+ string ArtistUnicode { get; }
+
+ ///
+ /// The author of this beatmap.
+ ///
+ string Author { get; } // eventually should be linked to a persisted User.
+
+ ///
+ /// The source of this beatmap.
+ ///
+ string Source { get; }
+
+ ///
+ /// The tags of this beatmap.
+ ///
+ string Tags { get; }
+
+ ///
+ /// The time in milliseconds to begin playing the track for preview purposes.
+ /// If -1, the track should begin playing at 40% of its length.
+ ///
+ int PreviewTime { get; }
+
+ ///
+ /// The filename of the audio file consumed by this beatmap.
+ ///
+ string AudioFile { get; }
+
+ ///
+ /// The filename of the background image file consumed by this beatmap.
+ ///
+ string BackgroundFile { get; }
+
+ bool IEquatable.Equals(IBeatmapMetadataInfo? other)
+ {
+ if (other == null)
+ return false;
+
+ return Title == other.Title
+ && TitleUnicode == other.TitleUnicode
+ && Artist == other.Artist
+ && ArtistUnicode == other.ArtistUnicode
+ && Author == other.Author
+ && Source == other.Source
+ && Tags == other.Tags
+ && PreviewTime == other.PreviewTime
+ && AudioFile == other.AudioFile
+ && BackgroundFile == other.BackgroundFile;
+ }
+ }
+}
diff --git a/osu.Game/Beatmaps/IBeatmapModelManager.cs b/osu.Game/Beatmaps/IBeatmapModelManager.cs
new file mode 100644
index 0000000000..8c243c2b77
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapModelManager.cs
@@ -0,0 +1,20 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Database;
+
+namespace osu.Game.Beatmaps
+{
+ public interface IBeatmapModelManager : IModelManager
+ {
+ ///
+ /// Provide an online lookup queue component to handle populating online beatmap metadata.
+ ///
+ BeatmapOnlineLookupQueue OnlineLookupQueue { set; }
+
+ ///
+ /// Provide a working beatmap cache, used to invalidate entries on changes.
+ ///
+ IWorkingBeatmapCache WorkingBeatmapCache { set; }
+ }
+}
diff --git a/osu.Game/Beatmaps/IBeatmapSetInfo.cs b/osu.Game/Beatmaps/IBeatmapSetInfo.cs
new file mode 100644
index 0000000000..0cfb0c4242
--- /dev/null
+++ b/osu.Game/Beatmaps/IBeatmapSetInfo.cs
@@ -0,0 +1,52 @@
+// 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.Game.Database;
+
+#nullable enable
+
+namespace osu.Game.Beatmaps
+{
+ ///
+ /// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive.
+ ///
+ public interface IBeatmapSetInfo : IHasOnlineID
+ {
+ ///
+ /// The date when this beatmap was imported.
+ ///
+ DateTimeOffset DateAdded { get; }
+
+ ///
+ /// The best-effort metadata representing this set. In the case metadata differs between contained beatmaps, one is arbitrarily chosen.
+ ///
+ IBeatmapMetadataInfo? Metadata { get; }
+
+ ///
+ /// All beatmaps contained in this set.
+ ///
+ IEnumerable Beatmaps { get; }
+
+ ///
+ /// All files used by this set.
+ ///
+ IEnumerable Files { get; }
+
+ ///
+ /// The maximum star difficulty of all beatmaps in this set.
+ ///
+ double MaxStarDifficulty { get; }
+
+ ///
+ /// The maximum playable length in milliseconds of all beatmaps in this set.
+ ///
+ double MaxLength { get; }
+
+ ///
+ /// The maximum BPM of all beatmaps in this set.
+ ///
+ double MaxBPM { get; }
+ }
+}
diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs
index 881e734292..3eb33f10d6 100644
--- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs
+++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs
@@ -11,5 +11,17 @@ namespace osu.Game.Beatmaps
/// The beatmap to lookup.
/// A instance correlating to the provided .
WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo);
+
+ ///
+ /// Invalidate a cache entry if it exists.
+ ///
+ /// The beatmap set info to invalidate any cached entries for.
+ void Invalidate(BeatmapSetInfo beatmapSetInfo);
+
+ ///
+ /// Invalidate a cache entry if it exists.
+ ///
+ /// The beatmap info to invalidate any cached entries for.
+ void Invalidate(BeatmapInfo beatmapInfo);
}
}
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 61760e69b0..18adecb7aa 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps
/// The applicable .
protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap);
- public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null)
+ public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null)
{
using (var cancellationSource = createCancellationTokenSource(timeout))
{
@@ -119,15 +119,12 @@ namespace osu.Game.Beatmaps
// Apply difficulty mods
if (mods.Any(m => m is IApplicableToDifficulty))
{
- converted.BeatmapInfo = converted.BeatmapInfo.Clone();
- converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone();
-
foreach (var mod in mods.OfType())
{
if (cancellationSource.IsCancellationRequested)
throw new BeatmapLoadTimeoutException(BeatmapInfo);
- mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty);
+ mod.ApplyToDifficulty(converted.Difficulty);
}
}
@@ -146,7 +143,7 @@ namespace osu.Game.Beatmaps
if (cancellationSource.IsCancellationRequested)
throw new BeatmapLoadTimeoutException(BeatmapInfo);
- obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty, cancellationSource.Token);
+ obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, cancellationSource.Token);
}
}
catch (OperationCanceledException)
diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs
index e117f1b82f..ad3e890b3a 100644
--- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs
@@ -201,12 +201,14 @@ namespace osu.Game.Beatmaps
{
var decoder = Decoder.GetDecoder(stream);
+ var storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename;
+
// todo: support loading from both set-wide storyboard *and* beatmap specific.
- if (BeatmapSetInfo?.StoryboardFile == null)
+ if (string.IsNullOrEmpty(storyboardFilename))
storyboard = decoder.Decode(stream);
else
{
- using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapSetInfo.StoryboardFile))))
+ using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename))))
storyboard = decoder.Decode(stream, secondaryStream);
}
}
diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs
index 680fec904f..95fbfa0f86 100644
--- a/osu.Game/Collections/ManageCollectionsDialog.cs
+++ b/osu.Game/Collections/ManageCollectionsDialog.cs
@@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
+using osu.Framework.Audio;
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.Audio.Effects;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -20,6 +22,8 @@ namespace osu.Game.Collections
private const double enter_duration = 500;
private const double exit_duration = 200;
+ private AudioFilter lowPassFilter;
+
[Resolved(CanBeNull = true)]
private CollectionManager collectionManager { get; set; }
@@ -36,7 +40,7 @@ namespace osu.Game.Collections
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load(OsuColour colours, AudioManager audio)
{
Children = new Drawable[]
{
@@ -108,7 +112,8 @@ namespace osu.Game.Collections
},
}
}
- }
+ },
+ lowPassFilter = new AudioFilter(audio.TrackMixer)
};
}
@@ -116,6 +121,7 @@ namespace osu.Game.Collections
{
base.PopIn();
+ lowPassFilter.CutoffTo(300, 100, Easing.OutCubic);
this.FadeIn(enter_duration, Easing.OutQuint);
this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint);
}
@@ -124,6 +130,8 @@ namespace osu.Game.Collections
{
base.PopOut();
+ lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
+
this.FadeOut(exit_duration, Easing.OutQuint);
this.ScaleTo(0.9f, exit_duration);
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 0c309bbddb..ee1a7e2900 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Database
///
/// The model type.
/// The associated file join type.
- public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager, IModelFileManager, IPresentImports
+ public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager, IModelFileManager, IPostImports
where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete
where TFileModel : class, INamedFileInfo, new()
{
@@ -132,13 +132,13 @@ namespace osu.Game.Database
return Import(notification, tasks);
}
- public async Task> Import(ProgressNotification notification, params ImportTask[] tasks)
+ public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks)
{
if (tasks.Length == 0)
{
notification.CompletionText = $"No {HumanisedModelName}s were found to import!";
notification.State = ProgressNotificationState.Completed;
- return Enumerable.Empty();
+ return Enumerable.Empty>();
}
notification.Progress = 0;
@@ -146,7 +146,7 @@ namespace osu.Game.Database
int current = 0;
- var imported = new List();
+ var imported = new List>();
bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size;
@@ -197,15 +197,15 @@ namespace osu.Game.Database
else
{
notification.CompletionText = imported.Count == 1
- ? $"Imported {imported.First()}!"
+ ? $"Imported {imported.First().Value}!"
: $"Imported {imported.Count} {HumanisedModelName}s!";
- if (imported.Count > 0 && PresentImport != null)
+ if (imported.Count > 0 && PostImport != null)
{
notification.CompletionText += " Click to view.";
notification.CompletionClickAction = () =>
{
- PresentImport?.Invoke(imported);
+ PostImport?.Invoke(imported);
return true;
};
}
@@ -224,11 +224,11 @@ namespace osu.Game.Database
/// Whether this is a low priority import.
/// An optional cancellation token.
/// The imported model, if successful.
- public async Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public async Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
- TModel import;
+ ILive import;
using (ArchiveReader reader = task.GetReader())
import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false);
@@ -243,13 +243,13 @@ namespace osu.Game.Database
}
catch (Exception e)
{
- LogForModel(import, $@"Could not delete original file after import ({task})", e);
+ LogForModel(import?.Value, $@"Could not delete original file after import ({task})", e);
}
return import;
}
- public Action> PresentImport { protected get; set; }
+ public Action>> PostImport { protected get; set; }
///
/// Silently import an item from an .
@@ -257,7 +257,7 @@ namespace osu.Game.Database
/// The archive to be imported.
/// Whether this is a low priority import.
/// An optional cancellation token.
- public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -268,7 +268,7 @@ namespace osu.Game.Database
model = CreateModel(archive);
if (model == null)
- return Task.FromResult(null);
+ return Task.FromResult>(new EntityFrameworkLive(null));
}
catch (TaskCanceledException)
{
@@ -343,7 +343,7 @@ namespace osu.Game.Database
/// An optional archive to use for model population.
/// Whether this is a low priority import.
/// An optional cancellation token.
- public virtual async Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
+ public virtual async Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
@@ -369,7 +369,7 @@ namespace osu.Game.Database
{
LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import.");
Undelete(existing);
- return existing;
+ return existing.ToEntityFrameworkLive();
}
LogForModel(item, @"Found existing (optimised) but failed pre-check.");
@@ -415,7 +415,7 @@ namespace osu.Game.Database
// existing item will be used; rollback new import and exit early.
rollback();
flushEvents(true);
- return existing;
+ return existing.ToEntityFrameworkLive();
}
LogForModel(item, @"Found existing but failed re-use check.");
@@ -448,7 +448,7 @@ namespace osu.Game.Database
}
flushEvents(true);
- return item;
+ return item.ToEntityFrameworkLive();
}, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false);
///
diff --git a/osu.Game/Database/EntityFrameworkLive.cs b/osu.Game/Database/EntityFrameworkLive.cs
new file mode 100644
index 0000000000..1d7b53911a
--- /dev/null
+++ b/osu.Game/Database/EntityFrameworkLive.cs
@@ -0,0 +1,34 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Database
+{
+ public class EntityFrameworkLive : ILive where T : class
+ {
+ public EntityFrameworkLive(T item)
+ {
+ Value = item;
+ }
+
+ public Guid ID => throw new InvalidOperationException();
+
+ public void PerformRead(Action perform)
+ {
+ perform(Value);
+ }
+
+ public TReturn PerformRead(Func perform)
+ {
+ return perform(Value);
+ }
+
+ public void PerformWrite(Action perform)
+ {
+ perform(Value);
+ }
+
+ public T Value { get; }
+ }
+}
diff --git a/osu.Game/Database/EntityFrameworkLiveExtensions.cs b/osu.Game/Database/EntityFrameworkLiveExtensions.cs
new file mode 100644
index 0000000000..cd0673675e
--- /dev/null
+++ b/osu.Game/Database/EntityFrameworkLiveExtensions.cs
@@ -0,0 +1,14 @@
+// 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.Database
+{
+ public static class EntityFrameworkLiveExtensions
+ {
+ public static ILive ToEntityFrameworkLive(this T item)
+ where T : class
+ {
+ return new EntityFrameworkLive(item);
+ }
+ }
+}
diff --git a/osu.Game/Database/IHasGuidPrimaryKey.cs b/osu.Game/Database/IHasGuidPrimaryKey.cs
index c9cd9b257a..f52dc5c8ef 100644
--- a/osu.Game/Database/IHasGuidPrimaryKey.cs
+++ b/osu.Game/Database/IHasGuidPrimaryKey.cs
@@ -11,6 +11,6 @@ namespace osu.Game.Database
{
[JsonIgnore]
[PrimaryKey]
- Guid ID { get; set; }
+ Guid ID { get; }
}
}
diff --git a/osu.Game/Database/IHasOnlineID.cs b/osu.Game/Database/IHasOnlineID.cs
new file mode 100644
index 0000000000..c55c461d2d
--- /dev/null
+++ b/osu.Game/Database/IHasOnlineID.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+namespace osu.Game.Database
+{
+ public interface IHasOnlineID
+ {
+ ///
+ /// The server-side ID representing this instance, if one exists.
+ ///
+ int? OnlineID { get; }
+ }
+}
diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/ILive.cs
new file mode 100644
index 0000000000..9359b09eaf
--- /dev/null
+++ b/osu.Game/Database/ILive.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Database
+{
+ ///
+ /// A wrapper to provide access to database backed classes in a thread-safe manner.
+ ///
+ /// The databased type.
+ public interface ILive where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more.
+ {
+ Guid ID { get; }
+
+ ///
+ /// Perform a read operation on this live object.
+ ///
+ /// The action to perform.
+ void PerformRead(Action perform);
+
+ ///
+ /// Perform a read operation on this live object.
+ ///
+ /// The action to perform.
+ TReturn PerformRead(Func perform);
+
+ ///
+ /// Perform a write operation on this live object.
+ ///
+ /// The action to perform.
+ void PerformWrite(Action perform);
+
+ ///
+ /// Resolve the value of this instance on the current thread's context.
+ ///
+ ///
+ /// After resolving the data should not be passed between threads.
+ ///
+ T Value { get; }
+ }
+}
diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs
new file mode 100644
index 0000000000..e94af01772
--- /dev/null
+++ b/osu.Game/Database/IModelImporter.cs
@@ -0,0 +1,65 @@
+// 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.Threading;
+using System.Threading.Tasks;
+using osu.Game.IO.Archives;
+using osu.Game.Overlays.Notifications;
+
+namespace osu.Game.Database
+{
+ ///
+ /// A class which handles importing of asociated models to the game store.
+ ///
+ /// The model type.
+ public interface IModelImporter : IPostNotifications
+ where TModel : class
+ {
+ ///
+ /// Import one or more items from filesystem .
+ ///
+ ///
+ /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority.
+ /// This will post notifications tracking progress.
+ ///
+ /// One or more archive locations on disk.
+ Task Import(params string[] paths);
+
+ Task Import(params ImportTask[] tasks);
+
+ Task>> Import(ProgressNotification notification, params ImportTask[] tasks);
+
+ ///
+ /// Import one from the filesystem and delete the file on success.
+ /// Note that this bypasses the UI flow and should only be used for special cases or testing.
+ ///
+ /// The containing data about the to import.
+ /// Whether this is a low priority import.
+ /// An optional cancellation token.
+ /// The imported model, if successful.
+ Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default);
+
+ ///
+ /// Silently import an item from an .
+ ///
+ /// The archive to be imported.
+ /// Whether this is a low priority import.
+ /// An optional cancellation token.
+ Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default);
+
+ ///
+ /// Silently import an item from a .
+ ///
+ /// The model to be imported.
+ /// An optional archive to use for model population.
+ /// Whether this is a low priority import.
+ /// An optional cancellation token.
+ Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
+
+ ///
+ /// A user displayable name for the model type associated with this manager.
+ ///
+ string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}";
+ }
+}
diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs
index 7bfc8dbee3..f5e401cdfb 100644
--- a/osu.Game/Database/IModelManager.cs
+++ b/osu.Game/Database/IModelManager.cs
@@ -4,12 +4,9 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Bindables;
using osu.Game.IO;
-using osu.Game.IO.Archives;
-using osu.Game.Overlays.Notifications;
namespace osu.Game.Database
{
@@ -17,7 +14,7 @@ namespace osu.Game.Database
/// Represents a model manager that publishes events when s are added or removed.
///
/// The model type.
- public interface IModelManager : IPostNotifications
+ public interface IModelManager : IModelImporter
where TModel : class
{
///
@@ -83,57 +80,11 @@ namespace osu.Game.Database
/// The item to restore
void Undelete(TModel item);
- ///
- /// Import one or more items from filesystem .
- ///
- ///
- /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority.
- /// This will post notifications tracking progress.
- ///
- /// One or more archive locations on disk.
- Task Import(params string[] paths);
-
- Task Import(params ImportTask[] tasks);
-
- Task> Import(ProgressNotification notification, params ImportTask[] tasks);
-
- ///
- /// Import one from the filesystem and delete the file on success.
- /// Note that this bypasses the UI flow and should only be used for special cases or testing.
- ///
- /// The containing data about the to import.
- /// Whether this is a low priority import.
- /// An optional cancellation token.
- /// The imported model, if successful.
- Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default);
-
- ///
- /// Silently import an item from an .
- ///
- /// The archive to be imported.
- /// Whether this is a low priority import.
- /// An optional cancellation token.
- Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default);
-
- ///
- /// Silently import an item from a .
- ///
- /// The model to be imported.
- /// An optional archive to use for model population.
- /// Whether this is a low priority import.
- /// An optional cancellation token.
- Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
-
///
/// Checks whether a given is already available in the local store.
///
/// The whose existence needs to be checked.
/// Whether the exists.
bool IsAvailableLocally(TModel model);
-
- ///
- /// A user displayable name for the model type associated with this manager.
- ///
- string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}";
}
}
diff --git a/osu.Game/Database/INamedFileUsage.cs b/osu.Game/Database/INamedFileUsage.cs
new file mode 100644
index 0000000000..e558ffe0fb
--- /dev/null
+++ b/osu.Game/Database/INamedFileUsage.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.IO;
+
+#nullable enable
+
+namespace osu.Game.Database
+{
+ ///
+ /// A usage of a file, with a local filename attached.
+ ///
+ public interface INamedFileUsage
+ {
+ ///
+ /// The underlying file on disk.
+ ///
+ IFileInfo File { get; }
+
+ ///
+ /// The filename for this usage.
+ ///
+ string Filename { get; }
+ }
+}
diff --git a/osu.Game/Database/IPresentImports.cs b/osu.Game/Database/IPostImports.cs
similarity index 76%
rename from osu.Game/Database/IPresentImports.cs
rename to osu.Game/Database/IPostImports.cs
index 39b495ebd5..f09285089a 100644
--- a/osu.Game/Database/IPresentImports.cs
+++ b/osu.Game/Database/IPostImports.cs
@@ -6,12 +6,12 @@ using System.Collections.Generic;
namespace osu.Game.Database
{
- public interface IPresentImports
+ public interface IPostImports
where TModel : class
{
///
/// Fired when the user requests to view the resulting import.
///
- public Action> PresentImport { set; }
+ public Action>> PostImport { set; }
}
}
diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs
index bf7feebdbf..0ff902a8bc 100644
--- a/osu.Game/Database/RealmContextFactory.cs
+++ b/osu.Game/Database/RealmContextFactory.cs
@@ -135,23 +135,46 @@ namespace osu.Game.Database
if (IsDisposed)
throw new ObjectDisposedException(nameof(RealmContextFactory));
+ // TODO: this can be added for safety once we figure how to bypass in test
+ // if (!ThreadSafety.IsUpdateThread)
+ // throw new InvalidOperationException($"{nameof(BlockAllOperations)} must be called from the update thread.");
+
Logger.Log(@"Blocking realm operations.", LoggingTarget.Database);
- contextCreationLock.Wait();
-
- lock (contextLock)
+ try
{
- context?.Dispose();
- context = null;
+ contextCreationLock.Wait();
+
+ lock (contextLock)
+ {
+ context?.Dispose();
+ context = null;
+ }
+
+ const int sleep_length = 200;
+ int timeout = 5000;
+
+ // see https://github.com/realm/realm-dotnet/discussions/2657
+ while (!Compact())
+ {
+ Thread.Sleep(sleep_length);
+ timeout -= sleep_length;
+
+ if (timeout < 0)
+ throw new TimeoutException("Took too long to acquire lock");
+ }
+ }
+ catch
+ {
+ contextCreationLock.Release();
+ throw;
}
- return new InvokeOnDisposal(this, endBlockingSection);
-
- static void endBlockingSection(RealmContextFactory factory)
+ return new InvokeOnDisposal(this, factory =>
{
factory.contextCreationLock.Release();
Logger.Log(@"Restoring realm operations.", LoggingTarget.Database);
- }
+ });
}
protected override void Dispose(bool isDisposing)
@@ -163,8 +186,8 @@ namespace osu.Game.Database
if (!IsDisposed)
{
- // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal.
- BlockAllOperations();
+ // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
+ contextCreationLock.Wait();
contextCreationLock.Dispose();
}
diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs
index 13c37ddfe9..ff81637efb 100644
--- a/osu.Game/Database/UserLookupCache.cs
+++ b/osu.Game/Database/UserLookupCache.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Database
createNewTask();
}
- List foundUsers = request.Result?.Users;
+ List foundUsers = request.Response?.Users;
if (foundUsers != null)
{
diff --git a/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs b/osu.Game/Graphics/ErrorTextFlowContainer.cs
similarity index 95%
rename from osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs
rename to osu.Game/Graphics/ErrorTextFlowContainer.cs
index 87ff4dd398..f17a2a2c3d 100644
--- a/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs
+++ b/osu.Game/Graphics/ErrorTextFlowContainer.cs
@@ -6,7 +6,7 @@ using osu.Framework.Graphics;
using osu.Game.Graphics.Containers;
using osuTK.Graphics;
-namespace osu.Game.Overlays.AccountCreation
+namespace osu.Game.Graphics
{
public class ErrorTextFlowContainer : OsuTextFlowContainer
{
diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs
index b6090d0e1a..edb484021c 100644
--- a/osu.Game/Graphics/OsuFont.cs
+++ b/osu.Game/Graphics/OsuFont.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Graphics
public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular);
+ public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular);
+
public static FontUsage Inter => GetFont(Typeface.Inter, weight: FontWeight.Regular);
///
@@ -57,6 +59,9 @@ namespace osu.Game.Graphics
case Typeface.Torus:
return "Torus";
+ case Typeface.TorusAlternate:
+ return "Torus-Alternate";
+
case Typeface.Inter:
return "Inter";
}
@@ -113,6 +118,7 @@ namespace osu.Game.Graphics
{
Venera,
Torus,
+ TorusAlternate,
Inter,
}
diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs
index 244658b75e..16555075d1 100644
--- a/osu.Game/Graphics/UserInterface/RollingCounter.cs
+++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs
@@ -25,7 +25,9 @@ namespace osu.Game.Graphics.UserInterface
set => current.Current = value;
}
- private SpriteText displayedCountSpriteText;
+ private IHasText displayedCountText;
+
+ public Drawable DrawableCount { get; private set; }
///
/// If true, the roll-up duration will be proportional to change in value.
@@ -72,16 +74,16 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load()
{
- displayedCountSpriteText = CreateSpriteText();
+ displayedCountText = CreateText();
UpdateDisplay();
- Child = displayedCountSpriteText;
+ Child = DrawableCount = (Drawable)displayedCountText;
}
protected void UpdateDisplay()
{
- if (displayedCountSpriteText != null)
- displayedCountSpriteText.Text = FormatCount(DisplayedCount);
+ if (displayedCountText != null)
+ displayedCountText.Text = FormatCount(DisplayedCount);
}
protected override void LoadComplete()
@@ -160,6 +162,15 @@ namespace osu.Game.Graphics.UserInterface
this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing);
}
+ ///
+ /// Creates the text. Delegates to by default.
+ ///
+ protected virtual IHasText CreateText() => CreateSpriteText();
+
+ ///
+ /// Creates an which may be used to display this counter's text.
+ /// May not be called if is overridden.
+ ///
protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 40f),
diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs
index e04bfb46cc..331546f9f8 100644
--- a/osu.Game/IO/FileInfo.cs
+++ b/osu.Game/IO/FileInfo.cs
@@ -6,7 +6,7 @@ using osu.Game.Database;
namespace osu.Game.IO
{
- public class FileInfo : IHasPrimaryKey
+ public class FileInfo : IHasPrimaryKey, IFileInfo
{
public int ID { get; set; }
diff --git a/osu.Game/IO/IFileInfo.cs b/osu.Game/IO/IFileInfo.cs
new file mode 100644
index 0000000000..080d8e57f5
--- /dev/null
+++ b/osu.Game/IO/IFileInfo.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+namespace osu.Game.IO
+{
+ ///
+ /// A representation of a tracked file.
+ ///
+ public interface IFileInfo
+ {
+ ///
+ /// SHA-256 hash of the file content.
+ ///
+ string Hash { get; }
+ }
+}
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index af14cdc7b3..94508e3a81 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -35,9 +35,8 @@ namespace osu.Game.Online.API
public string WebsiteRootUrl { get; }
- ///
- /// The username/email provided by the user when initiating a login.
- ///
+ public Exception LastLoginError { get; private set; }
+
public string ProvidedUsername { get; private set; }
private string password;
@@ -136,14 +135,23 @@ namespace osu.Game.Online.API
// save the username at this point, if the user requested for it to be.
config.SetValue(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
- if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password))
+ if (!authentication.HasValidAccessToken)
{
- //todo: this fails even on network-related issues. we should probably handle those differently.
- //NotificationOverlay.ShowMessage("Login failed!");
- log.Add(@"Login failed!");
- password = null;
- authentication.Clear();
- continue;
+ LastLoginError = null;
+
+ try
+ {
+ authentication.AuthenticateWithLogin(ProvidedUsername, password);
+ }
+ catch (Exception e)
+ {
+ //todo: this fails even on network-related issues. we should probably handle those differently.
+ LastLoginError = e;
+ log.Add(@"Login failed!");
+ password = null;
+ authentication.Clear();
+ continue;
+ }
}
var userReq = new GetUserRequest();
diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs
index cf17ed4b5d..d60c9cfe65 100644
--- a/osu.Game/Online/API/APIRequest.cs
+++ b/osu.Game/Online/API/APIRequest.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
using osu.Framework.Logging;
@@ -17,7 +18,11 @@ namespace osu.Game.Online.API
{
protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest(Uri);
- public T Result { get; private set; }
+ ///
+ /// The deserialised response object. May be null if the request or deserialisation failed.
+ ///
+ [CanBeNull]
+ public T Response { get; private set; }
///
/// Invoked on successful completion of an API request.
@@ -27,21 +32,21 @@ namespace osu.Game.Online.API
protected APIRequest()
{
- base.Success += () => Success?.Invoke(Result);
+ base.Success += () => Success?.Invoke(Response);
}
protected override void PostProcess()
{
base.PostProcess();
- Result = ((OsuJsonWebRequest)WebRequest)?.ResponseObject;
+ Response = ((OsuJsonWebRequest)WebRequest)?.ResponseObject;
}
internal void TriggerSuccess(T result)
{
- if (Result != null)
+ if (Response != null)
throw new InvalidOperationException("Attempted to trigger success more than once");
- Result = result;
+ Response = result;
TriggerSuccess();
}
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 1ba31db9fa..8f91a4d198 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.cs
@@ -32,6 +32,8 @@ namespace osu.Game.Online.API
public string WebsiteRootUrl => "http://localhost";
+ public Exception LastLoginError { get; private set; }
+
///
/// Provide handling logic for an arbitrary API request.
/// Should return true is a request was handled. If null or false return, the request will be failed with a .
@@ -40,6 +42,8 @@ namespace osu.Game.Online.API
private readonly Bindable state = new Bindable(APIState.Online);
+ private bool shouldFailNextLogin;
+
///
/// The current connectivity state of the API.
///
@@ -74,6 +78,18 @@ namespace osu.Game.Online.API
public void Login(string username, string password)
{
+ state.Value = APIState.Connecting;
+
+ if (shouldFailNextLogin)
+ {
+ LastLoginError = new APIException("Not powerful enough to login.", new ArgumentException(nameof(shouldFailNextLogin)));
+
+ state.Value = APIState.Offline;
+ shouldFailNextLogin = false;
+ return;
+ }
+
+ LastLoginError = null;
LocalUser.Value = new User
{
Username = username,
@@ -102,5 +118,7 @@ namespace osu.Game.Online.API
IBindable IAPIProvider.LocalUser => LocalUser;
IBindableList IAPIProvider.Friends => Friends;
IBindable IAPIProvider.Activity => Activity;
+
+ public void FailNextLogin() => shouldFailNextLogin = true;
}
}
diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs
index 5ad5367924..72ca37bcf4 100644
--- a/osu.Game/Online/API/IAPIProvider.cs
+++ b/osu.Game/Online/API/IAPIProvider.cs
@@ -3,6 +3,7 @@
#nullable enable
+using System;
using System.Threading.Tasks;
using osu.Framework.Bindables;
using osu.Game.Users;
@@ -55,6 +56,11 @@ namespace osu.Game.Online.API
///
string WebsiteRootUrl { get; }
+ ///
+ /// The last login error that occurred, if any.
+ ///
+ Exception? LastLoginError { get; }
+
///
/// The current connection state of the API.
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs
index bdc47aab8d..d79fc58d1c 100644
--- a/osu.Game/Online/API/OAuth.cs
+++ b/osu.Game/Online/API/OAuth.cs
@@ -1,8 +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;
using System.Diagnostics;
using System.Net.Http;
+using Newtonsoft.Json;
using osu.Framework.Bindables;
namespace osu.Game.Online.API
@@ -32,10 +34,10 @@ namespace osu.Game.Online.API
this.endpoint = endpoint;
}
- internal bool AuthenticateWithLogin(string username, string password)
+ internal void AuthenticateWithLogin(string username, string password)
{
- if (string.IsNullOrEmpty(username)) return false;
- if (string.IsNullOrEmpty(password)) return false;
+ if (string.IsNullOrEmpty(username)) throw new ArgumentException("Missing username.");
+ if (string.IsNullOrEmpty(password)) throw new ArgumentException("Missing password.");
using (var req = new AccessTokenRequestPassword(username, password)
{
@@ -49,13 +51,27 @@ namespace osu.Game.Online.API
{
req.Perform();
}
- catch
+ catch (Exception ex)
{
- return false;
+ Token.Value = null;
+
+ var throwableException = ex;
+
+ try
+ {
+ // attempt to decode a displayable error string.
+ var error = JsonConvert.DeserializeObject(req.GetResponseString() ?? string.Empty);
+ if (error != null)
+ throwableException = new APIException(error.UserDisplayableError, ex);
+ }
+ catch
+ {
+ }
+
+ throw throwableException;
}
Token.Value = req.ResponseObject;
- return true;
}
}
@@ -182,5 +198,19 @@ namespace osu.Game.Online.API
base.PrePerform();
}
}
+
+ private class OAuthError
+ {
+ public string UserDisplayableError => !string.IsNullOrEmpty(Hint) ? Hint : ErrorIdentifier;
+
+ [JsonProperty("error")]
+ public string ErrorIdentifier { get; set; }
+
+ [JsonProperty("hint")]
+ public string Hint { get; set; }
+
+ [JsonProperty("message")]
+ public string Message { get; set; }
+ }
}
}
diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs
index 87925b94c6..901f7365b8 100644
--- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs
+++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs
@@ -8,13 +8,13 @@ namespace osu.Game.Online.API.Requests
{
public class GetBeatmapRequest : APIRequest
{
- private readonly BeatmapInfo beatmap;
+ private readonly BeatmapInfo beatmapInfo;
- public GetBeatmapRequest(BeatmapInfo beatmap)
+ public GetBeatmapRequest(BeatmapInfo beatmapInfo)
{
- this.beatmap = beatmap;
+ this.beatmapInfo = beatmapInfo;
}
- protected override string Target => $@"beatmaps/lookup?id={beatmap.OnlineBeatmapID}&checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path ?? string.Empty)}";
+ protected override string Target => $@"beatmaps/lookup?id={beatmapInfo.OnlineBeatmapID}&checksum={beatmapInfo.MD5Hash}&filename={System.Uri.EscapeUriString(beatmapInfo.Path ?? string.Empty)}";
}
}
diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs
index 36e81a9348..651f8a06c5 100644
--- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs
+++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs
@@ -8,13 +8,13 @@ namespace osu.Game.Online.API.Requests
{
public class GetMessagesRequest : APIRequest>
{
- private readonly Channel channel;
+ public readonly Channel Channel;
public GetMessagesRequest(Channel channel)
{
- this.channel = channel;
+ Channel = channel;
}
- protected override string Target => $@"chat/channels/{channel.Id}/messages";
+ protected override string Target => $@"chat/channels/{Channel.Id}/messages";
}
}
diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs
index b4e0e44b2c..f3bf690ed5 100644
--- a/osu.Game/Online/API/Requests/GetScoresRequest.cs
+++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs
@@ -15,20 +15,20 @@ namespace osu.Game.Online.API.Requests
{
public class GetScoresRequest : APIRequest
{
- private readonly BeatmapInfo beatmap;
+ private readonly BeatmapInfo beatmapInfo;
private readonly BeatmapLeaderboardScope scope;
private readonly RulesetInfo ruleset;
private readonly IEnumerable mods;
- public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null)
+ public GetScoresRequest(BeatmapInfo beatmapInfo, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null)
{
- if (!beatmap.OnlineBeatmapID.HasValue)
+ if (!beatmapInfo.OnlineBeatmapID.HasValue)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
if (scope == BeatmapLeaderboardScope.Local)
throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard");
- this.beatmap = beatmap;
+ this.beatmapInfo = beatmapInfo;
this.scope = scope;
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
this.mods = mods ?? Array.Empty();
@@ -42,7 +42,7 @@ namespace osu.Game.Online.API.Requests
foreach (APILegacyScoreInfo score in r.Scores)
{
- score.Beatmap = beatmap;
+ score.BeatmapInfo = beatmapInfo;
score.OnlineRulesetID = ruleset.ID.Value;
}
@@ -50,12 +50,12 @@ namespace osu.Game.Online.API.Requests
if (userScore != null)
{
- userScore.Score.Beatmap = beatmap;
+ userScore.Score.BeatmapInfo = beatmapInfo;
userScore.Score.OnlineRulesetID = ruleset.ID.Value;
}
}
- protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores{createQueryParameters()}";
+ protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}";
private string createQueryParameters()
{
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
index 7343870dbc..c2a68c8ca1 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs
@@ -64,7 +64,7 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"max_combo")]
private int? maxCombo { get; set; }
- public virtual BeatmapInfo ToBeatmap(RulesetStore rulesets)
+ public virtual BeatmapInfo ToBeatmapInfo(RulesetStore rulesets)
{
var set = BeatmapSet?.ToBeatmapSet(rulesets);
diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
index f653a654ca..35963792d0 100644
--- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs
@@ -116,7 +116,7 @@ namespace osu.Game.Online.API.Requests.Responses
beatmapSet.Beatmaps = beatmaps?.Select(b =>
{
- var beatmap = b.ToBeatmap(rulesets);
+ var beatmap = b.ToBeatmapInfo(rulesets);
beatmap.BeatmapSet = beatmapSet;
beatmap.Metadata = beatmapSet.Metadata;
return beatmap;
diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
index 567df524b1..aaf2dccc82 100644
--- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
+++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs
@@ -37,7 +37,7 @@ namespace osu.Game.Online.API.Requests.Responses
OnlineScoreID = OnlineScoreID,
Date = Date,
PP = PP,
- Beatmap = Beatmap,
+ BeatmapInfo = BeatmapInfo,
RulesetID = OnlineRulesetID,
Hash = Replay ? "online" : string.Empty, // todo: temporary?
Rank = Rank,
@@ -100,7 +100,7 @@ namespace osu.Game.Online.API.Requests.Responses
public DateTimeOffset Date { get; set; }
[JsonProperty(@"beatmap")]
- public BeatmapInfo Beatmap { get; set; }
+ public BeatmapInfo BeatmapInfo { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
@@ -114,10 +114,10 @@ namespace osu.Game.Online.API.Requests.Responses
set
{
// extract the set ID to its correct place.
- Beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID };
+ BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID };
value.ID = 0;
- Beatmap.Metadata = value;
+ BeatmapInfo.Metadata = value;
}
}
diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs
index 4614fe29b7..10f7ca6fe2 100644
--- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs
@@ -15,8 +15,8 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty("count")]
public int PlayCount { get; set; }
- [JsonProperty]
- private BeatmapInfo beatmap { get; set; }
+ [JsonProperty("beatmap")]
+ private BeatmapInfo beatmapInfo { get; set; }
[JsonProperty]
private APIBeatmapSet beatmapSet { get; set; }
@@ -24,9 +24,9 @@ namespace osu.Game.Online.API.Requests.Responses
public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets)
{
BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets);
- beatmap.BeatmapSet = setInfo;
- beatmap.Metadata = setInfo.Metadata;
- return beatmap;
+ beatmapInfo.BeatmapSet = setInfo;
+ beatmapInfo.Metadata = setInfo.Metadata;
+ return beatmapInfo;
}
}
}
diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs
index 97a2fbdd5c..89eb00a45a 100644
--- a/osu.Game/Online/Chat/NowPlayingCommand.cs
+++ b/osu.Game/Online/Chat/NowPlayingCommand.cs
@@ -37,27 +37,27 @@ namespace osu.Game.Online.Chat
base.LoadComplete();
string verb;
- BeatmapInfo beatmap;
+ BeatmapInfo beatmapInfo;
switch (api.Activity.Value)
{
case UserActivity.InGame game:
verb = "playing";
- beatmap = game.Beatmap;
+ beatmapInfo = game.BeatmapInfo;
break;
case UserActivity.Editing edit:
verb = "editing";
- beatmap = edit.Beatmap;
+ beatmapInfo = edit.BeatmapInfo;
break;
default:
verb = "listening to";
- beatmap = currentBeatmap.Value.BeatmapInfo;
+ beatmapInfo = currentBeatmap.Value.BeatmapInfo;
break;
}
- var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString();
+ var beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString();
channelManager.PostMessage($"is {verb} {beatmapString}", true, target);
Expire();
diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs
index 4f8b27602b..e3ac9f603d 100644
--- a/osu.Game/Online/Leaderboards/Leaderboard.cs
+++ b/osu.Game/Online/Leaderboards/Leaderboard.cs
@@ -255,6 +255,7 @@ namespace osu.Game.Online.Leaderboards
}
private APIRequest getScoresRequest;
+ private ScheduledDelegate getScoresRequestCallback;
protected abstract bool IsOnlineScope { get; }
@@ -282,13 +283,16 @@ namespace osu.Game.Online.Leaderboards
getScoresRequest?.Cancel();
getScoresRequest = null;
+ getScoresRequestCallback?.Cancel();
+ getScoresRequestCallback = null;
+
pendingUpdateScores?.Cancel();
pendingUpdateScores = Schedule(() =>
{
PlaceholderState = PlaceholderState.Retrieving;
loading.Show();
- getScoresRequest = FetchScores(scores => Schedule(() =>
+ getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() =>
{
Scores = scores.ToArray();
PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores;
@@ -297,7 +301,7 @@ namespace osu.Game.Online.Leaderboards
if (getScoresRequest == null)
return;
- getScoresRequest.Failure += e => Schedule(() =>
+ getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() =>
{
if (e is OperationCanceledException)
return;
diff --git a/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs b/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs
index 973dccd528..00623282d3 100644
--- a/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs
+++ b/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs
@@ -13,9 +13,9 @@ namespace osu.Game.Online.Rooms
[JsonProperty("checksum")]
public string Checksum { get; set; }
- public override BeatmapInfo ToBeatmap(RulesetStore rulesets)
+ public override BeatmapInfo ToBeatmapInfo(RulesetStore rulesets)
{
- var b = base.ToBeatmap(rulesets);
+ var b = base.ToBeatmapInfo(rulesets);
b.MD5Hash = Checksum;
return b;
}
diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs
index 30c1d2f826..7ec34e70d5 100644
--- a/osu.Game/Online/Rooms/MultiplayerScore.cs
+++ b/osu.Game/Online/Rooms/MultiplayerScore.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Online.Rooms
OnlineScoreID = ID,
TotalScore = TotalScore,
MaxCombo = MaxCombo,
- Beatmap = playlistItem.Beatmap.Value,
+ BeatmapInfo = playlistItem.Beatmap.Value,
BeatmapInfoID = playlistItem.BeatmapID,
Ruleset = playlistItem.Ruleset.Value,
RulesetID = playlistItem.RulesetID,
diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs
index 1d409d4b56..48f1347fa1 100644
--- a/osu.Game/Online/Rooms/PlaylistItem.cs
+++ b/osu.Game/Online/Rooms/PlaylistItem.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Online.Rooms
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
{
- Beatmap.Value ??= apiBeatmap.ToBeatmap(rulesets);
+ Beatmap.Value ??= apiBeatmap.ToBeatmapInfo(rulesets);
Ruleset.Value ??= rulesets.GetRuleset(RulesetID);
Ruleset rulesetInstance = Ruleset.Value.CreateInstance();
diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs
index 8c617784b9..b597b2f214 100644
--- a/osu.Game/Online/Spectator/SpectatorClient.cs
+++ b/osu.Game/Online/Spectator/SpectatorClient.cs
@@ -134,7 +134,7 @@ namespace osu.Game.Online.Spectator
return Task.CompletedTask;
}
- public void BeginPlaying(GameplayBeatmap beatmap, Score score)
+ public void BeginPlaying(GameplayState state, Score score)
{
Debug.Assert(ThreadSafety.IsUpdateThread);
@@ -144,11 +144,11 @@ namespace osu.Game.Online.Spectator
IsPlaying = true;
// transfer state at point of beginning play
- currentState.BeatmapID = score.ScoreInfo.Beatmap.OnlineBeatmapID;
+ currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineBeatmapID;
currentState.RulesetID = score.ScoreInfo.RulesetID;
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
- currentBeatmap = beatmap.PlayableBeatmap;
+ currentBeatmap = state.Beatmap;
currentScore = score;
BeginPlayingInternal(currentState);
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 99925bb1fb..8a018f17d9 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -482,7 +482,7 @@ namespace osu.Game
return;
}
- var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID);
+ var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.BeatmapInfo.ID);
if (databasedBeatmap == null)
{
@@ -624,10 +624,10 @@ namespace osu.Game
SkinManager.PostNotification = n => Notifications.Post(n);
BeatmapManager.PostNotification = n => Notifications.Post(n);
- BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
+ BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value);
ScoreManager.PostNotification = n => Notifications.Post(n);
- ScoreManager.PresentImport = items => PresentScore(items.First());
+ ScoreManager.PostImport = items => PresentScore(items.First().Value);
// make config aware of how to lookup skins for on-screen display purposes.
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs
index adb819bf20..7f4fe8a943 100644
--- a/osu.Game/OsuGameBase.cs
+++ b/osu.Game/OsuGameBase.cs
@@ -13,24 +13,23 @@ using osu.Framework.Development;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.IO.Stores;
-using osu.Framework.Platform;
-using osu.Game.Beatmaps;
-using osu.Game.Configuration;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Cursor;
-using osu.Game.Online.API;
using osu.Framework.Graphics.Performance;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
+using osu.Framework.IO.Stores;
using osu.Framework.Logging;
-using osu.Framework.Threading;
+using osu.Framework.Platform;
using osu.Game.Audio;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Database;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Cursor;
using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.IO;
using osu.Game.Online;
+using osu.Game.Online.API;
using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
@@ -160,8 +159,6 @@ namespace osu.Game
private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST);
- private IBindable updateThreadState;
-
public OsuGameBase()
{
UseDevelopmentServer = DebugUtils.IsDebugBuild;
@@ -189,9 +186,6 @@ namespace osu.Game
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
- updateThreadState = Host.UpdateThread.State.GetBoundCopy();
- updateThreadState.BindValueChanged(updateThreadStateChanged);
-
AddInternal(realmFactory);
dependencies.CacheAs(Storage);
@@ -250,7 +244,7 @@ namespace osu.Game
List getBeatmapScores(BeatmapSetInfo set)
{
var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList();
- return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList();
+ return ScoreManager.QueryScores(s => beatmapIds.Contains(s.BeatmapInfo.ID)).ToList();
}
BeatmapManager.ItemRemoved.BindValueChanged(i =>
@@ -347,6 +341,11 @@ namespace osu.Game
AddFont(Resources, @"Fonts/Torus/Torus-SemiBold");
AddFont(Resources, @"Fonts/Torus/Torus-Bold");
+ AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-Regular");
+ AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-Light");
+ AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-SemiBold");
+ AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-Bold");
+
AddFont(Resources, @"Fonts/Inter/Inter-Regular");
AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic");
AddFont(Resources, @"Fonts/Inter/Inter-Light");
@@ -367,23 +366,6 @@ namespace osu.Game
AddFont(Resources, @"Fonts/Venera/Venera-Black");
}
- private IDisposable blocking;
-
- private void updateThreadStateChanged(ValueChangedEvent state)
- {
- switch (state.NewValue)
- {
- case GameThreadState.Running:
- blocking?.Dispose();
- blocking = null;
- break;
-
- case GameThreadState.Paused:
- blocking = realmFactory.BlockAllOperations();
- break;
- }
- }
-
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
index 5a6cde8229..683f4f0c49 100644
--- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs
+++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
@@ -38,16 +38,16 @@ namespace osu.Game.Overlays.BeatmapSet
}
}
- private BeatmapInfo beatmap;
+ private BeatmapInfo beatmapInfo;
- public BeatmapInfo Beatmap
+ public BeatmapInfo BeatmapInfo
{
- get => beatmap;
+ get => beatmapInfo;
set
{
- if (value == beatmap) return;
+ if (value == beatmapInfo) return;
- beatmap = value;
+ beatmapInfo = value;
updateDisplay();
}
@@ -57,7 +57,7 @@ namespace osu.Game.Overlays.BeatmapSet
{
bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToLocalisableString(@"0.##") ?? (LocalisableString)"-";
- if (beatmap == null)
+ if (beatmapInfo == null)
{
length.Value = string.Empty;
circleCount.Value = string.Empty;
@@ -65,11 +65,11 @@ namespace osu.Game.Overlays.BeatmapSet
}
else
{
- length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration());
- length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration();
+ length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration());
+ length.Value = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration();
- circleCount.Value = beatmap.OnlineInfo.CircleCount.ToLocalisableString(@"N0");
- sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToLocalisableString(@"N0");
+ circleCount.Value = beatmapInfo.OnlineInfo.CircleCount.ToLocalisableString(@"N0");
+ sliderCount.Value = beatmapInfo.OnlineInfo.SliderCount.ToLocalisableString(@"N0");
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
index 60e341d2ac..3df275c6d3 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs
@@ -178,21 +178,21 @@ namespace osu.Game.Overlays.BeatmapSet
}
starRatingContainer.FadeOut(100);
- Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap;
+ Beatmap.Value = Difficulties.FirstOrDefault()?.BeatmapInfo;
plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0;
favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0;
updateDifficultyButtons();
}
- private void showBeatmap(BeatmapInfo beatmap)
+ private void showBeatmap(BeatmapInfo beatmapInfo)
{
- version.Text = beatmap?.Version;
+ version.Text = beatmapInfo?.Version;
}
private void updateDifficultyButtons()
{
- Difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected);
+ Difficulties.Children.ToList().ForEach(diff => diff.State = diff.BeatmapInfo == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected);
}
public class DifficultiesContainer : FillFlowContainer
@@ -216,7 +216,7 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly Box backgroundBox;
private readonly DifficultyIcon icon;
- public readonly BeatmapInfo Beatmap;
+ public readonly BeatmapInfo BeatmapInfo;
public Action OnHovered;
public Action OnClicked;
@@ -241,9 +241,9 @@ namespace osu.Game.Overlays.BeatmapSet
}
}
- public DifficultySelectorButton(BeatmapInfo beatmap)
+ public DifficultySelectorButton(BeatmapInfo beatmapInfo)
{
- Beatmap = beatmap;
+ BeatmapInfo = beatmapInfo;
Size = new Vector2(size);
Margin = new MarginPadding { Horizontal = tile_spacing / 2 };
@@ -260,7 +260,7 @@ namespace osu.Game.Overlays.BeatmapSet
Alpha = 0.5f
}
},
- icon = new DifficultyIcon(beatmap, shouldShowTooltip: false)
+ icon = new DifficultyIcon(beatmapInfo, shouldShowTooltip: false)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -273,7 +273,7 @@ namespace osu.Game.Overlays.BeatmapSet
protected override bool OnHover(HoverEvent e)
{
fadeIn();
- OnHovered?.Invoke(Beatmap);
+ OnHovered?.Invoke(BeatmapInfo);
return base.OnHover(e);
}
@@ -286,7 +286,7 @@ namespace osu.Game.Overlays.BeatmapSet
protected override bool OnClick(ClickEvent e)
{
- OnClicked?.Invoke(Beatmap);
+ OnClicked?.Invoke(BeatmapInfo);
return base.OnClick(e);
}
diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
index c3b6444a24..dcf06ac7fb 100644
--- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
+++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs
@@ -211,7 +211,7 @@ namespace osu.Game.Overlays.BeatmapSet
Picker.Beatmap.ValueChanged += b =>
{
- Details.Beatmap = b.NewValue;
+ Details.BeatmapInfo = b.NewValue;
externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineBeatmapSetID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineBeatmapID}";
};
}
diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs
index 680487ffbb..92361ae4f8 100644
--- a/osu.Game/Overlays/BeatmapSet/Details.cs
+++ b/osu.Game/Overlays/BeatmapSet/Details.cs
@@ -37,16 +37,16 @@ namespace osu.Game.Overlays.BeatmapSet
}
}
- private BeatmapInfo beatmap;
+ private BeatmapInfo beatmapInfo;
- public BeatmapInfo Beatmap
+ public BeatmapInfo BeatmapInfo
{
- get => beatmap;
+ get => beatmapInfo;
set
{
- if (value == beatmap) return;
+ if (value == beatmapInfo) return;
- basic.Beatmap = advanced.Beatmap = beatmap = value;
+ basic.BeatmapInfo = advanced.BeatmapInfo = beatmapInfo = value;
}
}
diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs
index f9b8de9dba..61c660cbaa 100644
--- a/osu.Game/Overlays/BeatmapSet/Info.cs
+++ b/osu.Game/Overlays/BeatmapSet/Info.cs
@@ -24,10 +24,10 @@ namespace osu.Game.Overlays.BeatmapSet
public readonly Bindable BeatmapSet = new Bindable();
- public BeatmapInfo Beatmap
+ public BeatmapInfo BeatmapInfo
{
- get => successRate.Beatmap;
- set => successRate.Beatmap = value;
+ get => successRate.BeatmapInfo;
+ set => successRate.BeatmapInfo = value;
}
public Info()
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
index 8fe1d35b62..018faf2011 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs
@@ -172,7 +172,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
{
Text = score.MaxCombo.ToLocalisableString(@"0\x"),
Font = OsuFont.GetFont(size: text_size),
- Colour = score.MaxCombo == score.Beatmap?.MaxCombo ? highAccuracyColour : Color4.White
+ Colour = score.MaxCombo == score.BeatmapInfo?.MaxCombo ? highAccuracyColour : Color4.White
}
};
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
index fb1769fbe1..82657afc86 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs
@@ -74,7 +74,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
var topScore = ordered.Result.First();
- scoreTable.DisplayScores(ordered.Result, topScore.Beatmap?.Status.GrantsPerformancePoints() == true);
+ scoreTable.DisplayScores(ordered.Result, topScore.BeatmapInfo?.Status.GrantsPerformancePoints() == true);
scoreTable.Show();
var userScore = value.UserScore;
diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
index 883e83ce6e..630aa8fe53 100644
--- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
+++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs
@@ -115,7 +115,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores
accuracyColumn.Text = value.DisplayAccuracy;
maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x");
- ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0;
+ ppColumn.Alpha = value.BeatmapInfo?.Status.GrantsPerformancePoints() == true ? 1 : 0;
ppColumn.Text = value.PP?.ToLocalisableString(@"N0");
statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn);
diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs
index cde4589c98..4a9b8244a5 100644
--- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs
+++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs
@@ -23,16 +23,16 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly Bar successRate;
private readonly Container percentContainer;
- private BeatmapInfo beatmap;
+ private BeatmapInfo beatmapInfo;
- public BeatmapInfo Beatmap
+ public BeatmapInfo BeatmapInfo
{
- get => beatmap;
+ get => beatmapInfo;
set
{
- if (value == beatmap) return;
+ if (value == beatmapInfo) return;
- beatmap = value;
+ beatmapInfo = value;
updateDisplay();
}
@@ -40,15 +40,15 @@ namespace osu.Game.Overlays.BeatmapSet
private void updateDisplay()
{
- int passCount = beatmap?.OnlineInfo?.PassCount ?? 0;
- int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0;
+ int passCount = beatmapInfo?.OnlineInfo?.PassCount ?? 0;
+ int playCount = beatmapInfo?.OnlineInfo?.PlayCount ?? 0;
var rate = playCount != 0 ? (float)passCount / playCount : 0;
successPercent.Text = rate.ToLocalisableString(@"0.#%");
successRate.Length = rate;
percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic);
- Graph.Metrics = beatmap?.Metrics;
+ Graph.Metrics = beatmapInfo?.Metrics;
}
public SuccessRate()
diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs
index bdb3715e73..f987b57d6e 100644
--- a/osu.Game/Overlays/BeatmapSetOverlay.cs
+++ b/osu.Game/Overlays/BeatmapSetOverlay.cs
@@ -61,7 +61,7 @@ namespace osu.Game.Overlays
Header.HeaderContent.Picker.Beatmap.ValueChanged += b =>
{
- info.Beatmap = b.NewValue;
+ info.BeatmapInfo = b.NewValue;
ScrollFlow.ScrollToStart();
};
}
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index 25c5154d4a..4b27335c7c 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -284,6 +284,10 @@ namespace osu.Game.Overlays
if (currentChannel.Value != e.NewValue)
return;
+ // check once more to ensure the channel hasn't since been removed from the loaded channels list (may have been left by some automated means).
+ if (!loadedChannels.Contains(loaded))
+ return;
+
loading.Hide();
currentChannelContainer.Clear(false);
@@ -444,10 +448,9 @@ namespace osu.Game.Overlays
if (loaded != null)
{
- loadedChannels.Remove(loaded);
-
// Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared
// to ensure that the previous channel doesn't get updated after it's disposed
+ loadedChannels.Remove(loaded);
currentChannelContainer.Remove(loaded);
loaded.Dispose();
}
diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs
index f051e09c08..9db0f34d1b 100644
--- a/osu.Game/Overlays/DialogOverlay.cs
+++ b/osu.Game/Overlays/DialogOverlay.cs
@@ -7,7 +7,10 @@ using osu.Game.Overlays.Dialog;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
using osu.Framework.Input.Events;
+using osu.Game.Audio.Effects;
namespace osu.Game.Overlays
{
@@ -18,6 +21,8 @@ namespace osu.Game.Overlays
protected override string PopInSampleName => "UI/dialog-pop-in";
protected override string PopOutSampleName => "UI/dialog-pop-out";
+ private AudioFilter lowPassFilter;
+
public PopupDialog CurrentDialog { get; private set; }
public DialogOverlay()
@@ -34,6 +39,12 @@ namespace osu.Game.Overlays
Origin = Anchor.BottomCentre;
}
+ [BackgroundDependencyLoader]
+ private void load(AudioManager audio)
+ {
+ AddInternal(lowPassFilter = new AudioFilter(audio.TrackMixer));
+ }
+
public void Push(PopupDialog dialog)
{
if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return;
@@ -71,12 +82,15 @@ namespace osu.Game.Overlays
{
base.PopIn();
this.FadeIn(PopupDialog.ENTER_DURATION, Easing.OutQuint);
+ lowPassFilter.CutoffTo(300, 100, Easing.OutCubic);
}
protected override void PopOut()
{
base.PopOut();
+ lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
+
if (CurrentDialog?.State.Value == Visibility.Visible)
{
CurrentDialog.Hide();
diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs
index e43b84d52a..f7842dcd30 100644
--- a/osu.Game/Overlays/Login/LoginForm.cs
+++ b/osu.Game/Overlays/Login/LoginForm.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
@@ -42,6 +43,9 @@ namespace osu.Game.Overlays.Login
Spacing = new Vector2(0, 5);
AutoSizeAxes = Axes.Y;
RelativeSizeAxes = Axes.X;
+
+ ErrorTextFlowContainer errorText;
+
Children = new Drawable[]
{
username = new OsuTextBox
@@ -57,6 +61,11 @@ namespace osu.Game.Overlays.Login
RelativeSizeAxes = Axes.X,
TabbableContentContainer = this,
},
+ errorText = new ErrorTextFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ },
new SettingsCheckbox
{
LabelText = "Remember username",
@@ -97,6 +106,9 @@ namespace osu.Game.Overlays.Login
};
password.OnCommit += (sender, newText) => performLogin();
+
+ if (api?.LastLoginError?.Message is string error)
+ errorText.AddErrors(new[] { error });
}
public override bool AcceptsFocus => true;
@@ -108,4 +120,4 @@ namespace osu.Game.Overlays.Login
Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); });
}
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs
index 571b14428e..ef25de77c6 100644
--- a/osu.Game/Overlays/Music/PlaylistItem.cs
+++ b/osu.Game/Overlays/Music/PlaylistItem.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Music
{
Padding = new MarginPadding { Left = 5 };
- FilterTerms = item.Metadata.SearchableTerms;
+ FilterTerms = item.Metadata.GetSearchableTerms();
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
index a8a4cfc365..7812a81f30 100644
--- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
+++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs
@@ -15,12 +15,12 @@ namespace osu.Game.Overlays.Profile.Sections
///
public abstract class BeatmapMetadataContainer : OsuHoverContainer
{
- private readonly BeatmapInfo beatmap;
+ private readonly BeatmapInfo beatmapInfo;
- protected BeatmapMetadataContainer(BeatmapInfo beatmap)
+ protected BeatmapMetadataContainer(BeatmapInfo beatmapInfo)
: base(HoverSampleSet.Submit)
{
- this.beatmap = beatmap;
+ this.beatmapInfo = beatmapInfo;
AutoSizeAxes = Axes.Both;
}
@@ -30,19 +30,19 @@ namespace osu.Game.Overlays.Profile.Sections
{
Action = () =>
{
- if (beatmap.OnlineBeatmapID != null)
- beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value);
- else if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null)
- beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value);
+ if (beatmapInfo.OnlineBeatmapID != null)
+ beatmapSetOverlay?.FetchAndShowBeatmap(beatmapInfo.OnlineBeatmapID.Value);
+ else if (beatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null)
+ beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapInfo.BeatmapSet.OnlineBeatmapSetID.Value);
};
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
- Children = CreateText(beatmap),
+ Children = CreateText(beatmapInfo),
};
}
- protected abstract Drawable[] CreateText(BeatmapInfo beatmap);
+ protected abstract Drawable[] CreateText(BeatmapInfo beatmapInfo);
}
}
diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
index a419bef233..2c6fa76ca4 100644
--- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
+++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs
@@ -22,12 +22,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
private const int cover_width = 100;
private const int corner_radius = 6;
- private readonly BeatmapInfo beatmap;
+ private readonly BeatmapInfo beatmapInfo;
private readonly int playCount;
- public DrawableMostPlayedBeatmap(BeatmapInfo beatmap, int playCount)
+ public DrawableMostPlayedBeatmap(BeatmapInfo beatmapInfo, int playCount)
{
- this.beatmap = beatmap;
+ this.beatmapInfo = beatmapInfo;
this.playCount = playCount;
RelativeSizeAxes = Axes.X;
@@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
{
RelativeSizeAxes = Axes.Y,
Width = cover_width,
- BeatmapSet = beatmap.BeatmapSet,
+ BeatmapSet = beatmapInfo.BeatmapSet,
},
new Container
{
@@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
- new MostPlayedBeatmapMetadataContainer(beatmap),
+ new MostPlayedBeatmapMetadataContainer(beatmapInfo),
new LinkFlowContainer(t =>
{
t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular);
@@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
}.With(d =>
{
d.AddText("mapped by ");
- d.AddUserLink(beatmap.Metadata.Author);
+ d.AddUserLink(beatmapInfo.Metadata.Author);
}),
}
},
@@ -120,23 +120,23 @@ namespace osu.Game.Overlays.Profile.Sections.Historical
private class MostPlayedBeatmapMetadataContainer : BeatmapMetadataContainer
{
- public MostPlayedBeatmapMetadataContainer(BeatmapInfo beatmap)
- : base(beatmap)
+ public MostPlayedBeatmapMetadataContainer(BeatmapInfo beatmapInfo)
+ : base(beatmapInfo)
{
}
- protected override Drawable[] CreateText(BeatmapInfo beatmap) => new Drawable[]
+ protected override Drawable[] CreateText(BeatmapInfo beatmapInfo) => new Drawable[]
{
new OsuSpriteText
{
Text = new RomanisableString(
- $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ",
- $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "),
+ $"{beatmapInfo.Metadata.TitleUnicode ?? beatmapInfo.Metadata.Title} [{beatmapInfo.Version}] ",
+ $"{beatmapInfo.Metadata.Title ?? beatmapInfo.Metadata.TitleUnicode} [{beatmapInfo.Version}] "),
Font = OsuFont.GetFont(weight: FontWeight.Bold)
},
new OsuSpriteText
{
- Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
+ Text = "by " + new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Regular)
},
};
diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
index 713303285a..3561e9700e 100644
--- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
+++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs
@@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
Spacing = new Vector2(0, 2),
Children = new Drawable[]
{
- new ScoreBeatmapMetadataContainer(Score.Beatmap),
+ new ScoreBeatmapMetadataContainer(Score.BeatmapInfo),
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
@@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
{
new OsuSpriteText
{
- Text = $"{Score.Beatmap.Version}",
+ Text = $"{Score.BeatmapInfo.Version}",
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
Colour = colours.Yellow
},
@@ -245,27 +245,27 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
private class ScoreBeatmapMetadataContainer : BeatmapMetadataContainer
{
- public ScoreBeatmapMetadataContainer(BeatmapInfo beatmap)
- : base(beatmap)
+ public ScoreBeatmapMetadataContainer(BeatmapInfo beatmapInfo)
+ : base(beatmapInfo)
{
}
- protected override Drawable[] CreateText(BeatmapInfo beatmap) => new Drawable[]
+ protected override Drawable[] CreateText(BeatmapInfo beatmapInfo) => new Drawable[]
{
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = new RomanisableString(
- $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} ",
- $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} "),
+ $"{beatmapInfo.Metadata.TitleUnicode ?? beatmapInfo.Metadata.Title} ",
+ $"{beatmapInfo.Metadata.Title ?? beatmapInfo.Metadata.TitleUnicode} "),
Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true)
},
new OsuSpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
- Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist),
+ Text = "by " + new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist),
Font = OsuFont.GetFont(size: 12, italics: true)
},
};
diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs
index b8bdef925e..80ce2e038d 100644
--- a/osu.Game/Overlays/RankingsOverlay.cs
+++ b/osu.Game/Overlays/RankingsOverlay.cs
@@ -143,19 +143,27 @@ namespace osu.Game.Overlays
switch (request)
{
case GetUserRankingsRequest userRequest:
+ if (userRequest.Response == null)
+ return null;
+
switch (userRequest.Type)
{
case UserRankingsType.Performance:
- return new PerformanceTable(1, userRequest.Result.Users);
+ return new PerformanceTable(1, userRequest.Response.Users);
case UserRankingsType.Score:
- return new ScoresTable(1, userRequest.Result.Users);
+ return new ScoresTable(1, userRequest.Response.Users);
}
return null;
case GetCountryRankingsRequest countryRequest:
- return new CountriesTable(1, countryRequest.Result.Countries);
+ {
+ if (countryRequest.Response == null)
+ return null;
+
+ return new CountriesTable(1, countryRequest.Response.Countries);
+ }
}
return null;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
index 0e8e10c086..2cc2857e9b 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
@@ -75,5 +75,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Content.CornerRadius = 5;
}
+
+ // Empty FilterTerms so that the ResetButton is visible only when the whole subsection is visible.
+ public override IEnumerable FilterTerms => Enumerable.Empty();
}
}
diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
index 224c9178ae..200bbf3f92 100644
--- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
+++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs
@@ -7,6 +7,8 @@ using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills;
using osu.Game.Rulesets.Mods;
@@ -16,6 +18,14 @@ namespace osu.Game.Rulesets.Difficulty
{
public abstract class DifficultyCalculator
{
+ ///
+ /// The beatmap for which difficulty will be calculated.
+ ///
+ protected IBeatmap Beatmap { get; private set; }
+
+ private Mod[] playableMods;
+ private double clockRate;
+
private readonly Ruleset ruleset;
private readonly WorkingBeatmap beatmap;
@@ -32,14 +42,45 @@ namespace osu.Game.Rulesets.Difficulty
/// A structure describing the difficulty of the beatmap.
public DifficultyAttributes Calculate(params Mod[] mods)
{
- mods = mods.Select(m => m.DeepClone()).ToArray();
+ preProcess(mods);
- IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
+ var skills = CreateSkills(Beatmap, playableMods, clockRate);
- var track = new TrackVirtual(10000);
- mods.OfType().ForEach(m => m.ApplyToTrack(track));
+ if (!Beatmap.HitObjects.Any())
+ return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
- return calculate(playableBeatmap, mods, track.Rate);
+ foreach (var hitObject in getDifficultyHitObjects())
+ {
+ foreach (var skill in skills)
+ skill.ProcessInternal(hitObject);
+ }
+
+ return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate);
+ }
+
+ public List CalculateTimed(params Mod[] mods)
+ {
+ preProcess(mods);
+
+ var attribs = new List();
+
+ if (!Beatmap.HitObjects.Any())
+ return attribs;
+
+ var skills = CreateSkills(Beatmap, playableMods, clockRate);
+ var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap);
+
+ foreach (var hitObject in getDifficultyHitObjects())
+ {
+ progressiveBeatmap.HitObjects.Add(hitObject.BaseObject);
+
+ foreach (var skill in skills)
+ skill.ProcessInternal(hitObject);
+
+ attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)));
+ }
+
+ return attribs;
}
///
@@ -57,24 +98,24 @@ namespace osu.Game.Rulesets.Difficulty
}
}
- private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate)
+ ///
+ /// Retrieves the s to calculate against.
+ ///
+ private IEnumerable getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(Beatmap, clockRate));
+
+ ///
+ /// Performs required tasks before every calculation.
+ ///
+ /// The original list of s.
+ private void preProcess(Mod[] mods)
{
- var skills = CreateSkills(beatmap, mods, clockRate);
+ playableMods = mods.Select(m => m.DeepClone()).ToArray();
- if (!beatmap.HitObjects.Any())
- return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
+ Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods);
- var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList();
-
- foreach (var hitObject in difficultyHitObjects)
- {
- foreach (var skill in skills)
- {
- skill.ProcessInternal(hitObject);
- }
- }
-
- return CreateDifficultyAttributes(beatmap, mods, skills, clockRate);
+ var track = new TrackVirtual(10000);
+ playableMods.OfType().ForEach(m => m.ApplyToTrack(track));
+ clockRate = track.Rate;
}
///
@@ -86,7 +127,7 @@ namespace osu.Game.Rulesets.Difficulty
=> input.OrderBy(h => h.BaseObject.StartTime);
///
- /// Creates all combinations which adjust the difficulty.
+ /// Creates all combinations which adjust the difficulty.
///
public Mod[] CreateDifficultyAdjustmentModCombinations()
{
@@ -154,14 +195,15 @@ namespace osu.Game.Rulesets.Difficulty
}
///
- /// Retrieves all s which adjust the difficulty.
+ /// Retrieves all s which adjust the difficulty.
///
protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty();
///
/// Creates to describe beatmap's calculated difficulty.
///
- /// The whose difficulty was calculated.
+ /// The whose difficulty was calculated.
+ /// This may differ from in the case of timed calculation.
/// The s that difficulty was calculated with.
/// The skills which processed the beatmap.
/// The rate at which the gameplay clock is run at.
@@ -178,10 +220,58 @@ namespace osu.Game.Rulesets.Difficulty
///
/// Creates the s to calculate the difficulty of an .
///
- /// The whose difficulty will be calculated.
+ /// The whose difficulty will be calculated.
+ /// This may differ from in the case of timed calculation.
/// Mods to calculate difficulty with.
/// Clockrate to calculate difficulty with.
/// The s.
protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate);
+
+ ///
+ /// Used to calculate timed difficulty attributes, where only a subset of hitobjects should be visible at any point in time.
+ ///
+ private class ProgressiveCalculationBeatmap : IBeatmap
+ {
+ private readonly IBeatmap baseBeatmap;
+
+ public ProgressiveCalculationBeatmap(IBeatmap baseBeatmap)
+ {
+ this.baseBeatmap = baseBeatmap;
+ }
+
+ public readonly List HitObjects = new List();
+
+ IReadOnlyList IBeatmap.HitObjects => HitObjects;
+
+ #region Delegated IBeatmap implementation
+
+ public BeatmapInfo BeatmapInfo
+ {
+ get => baseBeatmap.BeatmapInfo;
+ set => baseBeatmap.BeatmapInfo = value;
+ }
+
+ public ControlPointInfo ControlPointInfo
+ {
+ get => baseBeatmap.ControlPointInfo;
+ set => baseBeatmap.ControlPointInfo = value;
+ }
+
+ public BeatmapMetadata Metadata => baseBeatmap.Metadata;
+
+ public BeatmapDifficulty Difficulty
+ {
+ get => baseBeatmap.Difficulty;
+ set => baseBeatmap.Difficulty = value;
+ }
+
+ public List Breaks => baseBeatmap.Breaks;
+ public double TotalBreakTime => baseBeatmap.TotalBreakTime;
+ public IEnumerable GetStatistics() => baseBeatmap.GetStatistics();
+ public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength();
+ public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone());
+
+ #endregion
+ }
}
}
diff --git a/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs
new file mode 100644
index 0000000000..973b2dacb2
--- /dev/null
+++ b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs
@@ -0,0 +1,25 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+
+namespace osu.Game.Rulesets.Difficulty
+{
+ ///
+ /// Wraps a object and adds a time value for which the attribute is valid.
+ /// Output by .
+ ///
+ public class TimedDifficultyAttributes : IComparable
+ {
+ public readonly double Time;
+ public readonly DifficultyAttributes Attributes;
+
+ public TimedDifficultyAttributes(double time, DifficultyAttributes attributes)
+ {
+ Time = time;
+ Attributes = attributes;
+ }
+
+ public int CompareTo(TimedDifficultyAttributes other) => Time.CompareTo(other.Time);
+ }
+}
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 8090fcbd32..b41e0442bc 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -392,7 +392,7 @@ namespace osu.Game.Rulesets.Edit
public override float GetBeatSnapDistanceAt(double referenceTime)
{
DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime);
- return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor);
+ return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor);
}
public override float DurationToDistance(double referenceTime, double duration)
diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
index 82e90399c9..0c0c5990d1 100644
--- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
+++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs
@@ -109,10 +109,10 @@ namespace osu.Game.Rulesets.Edit
}
///
- /// Invokes ,
+ /// Invokes ,
/// refreshing and parameters for the .
///
- protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty);
+ protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false;
diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs
index 13cc41f8e0..dd2ad2cbfa 100644
--- a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs
+++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs
@@ -14,15 +14,15 @@ namespace osu.Game.Rulesets.Filter
public interface IRulesetFilterCriteria
{
///
- /// Checks whether the supplied satisfies ruleset-specific custom criteria,
+ /// Checks whether the supplied satisfies ruleset-specific custom criteria,
/// in addition to the ones mandated by song select.
///
- /// The beatmap to test the criteria against.
+ /// The beatmap to test the criteria against.
///
/// true if the beatmap matches the ruleset-specific custom filtering criteria,
/// false otherwise.
///
- bool Matches(BeatmapInfo beatmap);
+ bool Matches(BeatmapInfo beatmapInfo);
///
/// Attempts to parse a single custom keyword criterion, given by the user via the song select search box.
diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs
new file mode 100644
index 0000000000..779433dc81
--- /dev/null
+++ b/osu.Game/Rulesets/IRulesetInfo.cs
@@ -0,0 +1,47 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Database;
+
+#nullable enable
+
+namespace osu.Game.Rulesets
+{
+ ///
+ /// A representation of a ruleset's metadata.
+ ///
+ public interface IRulesetInfo : IHasOnlineID
+ {
+ ///
+ /// The user-exposed name of this ruleset.
+ ///
+ string Name { get; }
+
+ ///
+ /// An acronym defined by the ruleset that can be used as a permanent identifier.
+ ///
+ string ShortName { get; }
+
+ ///
+ /// A string representation of this ruleset, to be used with reflection to instantiate the ruleset represented by this metadata.
+ ///
+ string InstantiationInfo { get; }
+
+ Ruleset? CreateInstance()
+ {
+ var type = Type.GetType(InstantiationInfo);
+
+ if (type == null)
+ return null;
+
+ var ruleset = Activator.CreateInstance(type) as Ruleset;
+
+ // overwrite the pre-populated RulesetInfo with a potentially database attached copy.
+ // TODO: figure if we still want/need this after switching to realm.
+ // ruleset.RulesetInfo = this;
+
+ return ruleset;
+ }
+ }
+}
diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs
index e4304795f2..6cfae0b085 100644
--- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs
+++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods
///
/// A function that can extract the current value of this setting from a beatmap difficulty for display purposes.
///
- public Func ReadCurrentFromDifficulty;
+ public Func ReadCurrentFromDifficulty;
public float Precision
{
diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
index b78c30e8a5..eefa1531c4 100644
--- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
+++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs
@@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mods
}
}
- public void ReadFromDifficulty(BeatmapDifficulty difficulty)
+ public void ReadFromDifficulty(IBeatmapDifficultyInfo difficulty)
{
}
diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs
index 4edcb0b074..da838f9ea6 100644
--- a/osu.Game/Rulesets/Mods/ModHardRock.cs
+++ b/osu.Game/Rulesets/Mods/ModHardRock.cs
@@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods
public override string Description => "Everything just got a bit harder...";
public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) };
- public void ReadFromDifficulty(BeatmapDifficulty difficulty)
+ public void ReadFromDifficulty(IBeatmapDifficultyInfo difficulty)
{
}
diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs
index ae0cb895bc..0b159819d4 100644
--- a/osu.Game/Rulesets/Objects/HitObject.cs
+++ b/osu.Game/Rulesets/Objects/HitObject.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Objects
/// The control points.
/// The difficulty settings to use.
/// The cancellation token.
- public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty, CancellationToken cancellationToken = default)
+ public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default)
{
ApplyDefaultsToSelf(controlPointInfo, difficulty);
@@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Objects
DefaultsApplied?.Invoke(this);
}
- protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode;
diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
index df569b91c1..e1de82ade7 100644
--- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
+++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
public double Velocity = 1;
- protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs
index 59ec9cdd7e..ca6a083a58 100644
--- a/osu.Game/Rulesets/RulesetInfo.cs
+++ b/osu.Game/Rulesets/RulesetInfo.cs
@@ -10,7 +10,7 @@ using osu.Framework.Testing;
namespace osu.Game.Rulesets
{
[ExcludeFromDynamicCompile]
- public class RulesetInfo : IEquatable
+ public class RulesetInfo : IEquatable, IRulesetInfo
{
public int? ID { get; set; }
@@ -54,5 +54,11 @@ namespace osu.Game.Rulesets
}
public override string ToString() => Name ?? $"{Name} ({ShortName}) ID: {ID}";
+
+ #region Implementation of IHasOnlineID
+
+ public int? OnlineID => ID;
+
+ #endregion
}
}
diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
index cae41e22f4..dfeb6b4788 100644
--- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Scoring
.First()
)));
- targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
+ targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, min_health_target, mid_health_target, max_health_target);
// Add back a portion of the amount of HP to be drained, depending on the lenience requested.
targetMinimumHealth += drainLenience * (1 - targetMinimumHealth);
diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs
index 410614de07..3ffd1eb66b 100644
--- a/osu.Game/Rulesets/Scoring/HitWindows.cs
+++ b/osu.Game/Rulesets/Scoring/HitWindows.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Scoring
{
foreach (var range in GetRanges())
{
- var value = BeatmapDifficulty.DifficultyRange(difficulty, (range.Min, range.Average, range.Max));
+ var value = IBeatmapDifficultyInfo.DifficultyRange(difficulty, (range.Min, range.Average, range.Max));
switch (range.Result)
{
diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
index 201a05e569..ed4a16f0e8 100644
--- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
+++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs
@@ -18,6 +18,11 @@ namespace osu.Game.Rulesets.Scoring
///
public event Action NewJudgement;
+ ///
+ /// Invoked when a judgement is reverted, usually due to rewinding gameplay.
+ ///
+ public event Action JudgementReverted;
+
///
/// The maximum number of hits that can be judged.
///
@@ -71,6 +76,8 @@ namespace osu.Game.Rulesets.Scoring
JudgedHits--;
RevertResultInternal(result);
+
+ JudgementReverted?.Invoke(result);
}
///
diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs
index b57c224059..976f95cef8 100644
--- a/osu.Game/Rulesets/UI/ReplayRecorder.cs
+++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.UI
private SpectatorClient spectatorClient { get; set; }
[Resolved]
- private GameplayBeatmap gameplayBeatmap { get; set; }
+ private GameplayState gameplayState { get; set; }
protected ReplayRecorder(Score target)
{
@@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.UI
inputManager = GetContainingInputManager();
- spectatorClient?.BeginPlaying(gameplayBeatmap, target);
+ spectatorClient?.BeginPlaying(gameplayState, target);
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
index 7b30bb9574..041c5ebef5 100644
--- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
@@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
maxDuration = duration;
// The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths
// the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here
- baseBeatLength = timingPoints[i].BeatLength / Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
+ baseBeatLength = timingPoints[i].BeatLength / Beatmap.Difficulty.SliderMultiplier;
}
}
}
@@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
return new MultiplierControlPoint(c.Time)
{
- Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier,
+ Velocity = Beatmap.Difficulty.SliderMultiplier,
BaseBeatLength = baseBeatLength,
TimingPoint = lastTimingPoint,
DifficultyPoint = lastDifficultyPoint
@@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
ControlPoints.AddRange(timingChanges);
if (ControlPoints.Count == 0)
- ControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier });
+ ControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.Difficulty.SliderMultiplier });
}
protected override void LoadComplete()
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index 2e1a29372d..a1658b4cf3 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -70,7 +70,7 @@ namespace osu.Game.Scoring.Legacy
scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod()).ToArray();
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
- scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;
+ scoreInfo.BeatmapInfo = currentBeatmap.BeatmapInfo;
/* score.HpGraphString = */
sr.ReadString();
@@ -119,7 +119,7 @@ namespace osu.Game.Scoring.Legacy
// before returning for database import, we must restore the database-sourced BeatmapInfo.
// if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
- score.ScoreInfo.Beatmap = workingBeatmap.BeatmapInfo;
+ score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo;
return score;
}
diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
index 288552879c..58e4192f77 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy
this.score = score;
this.beatmap = beatmap;
- if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3)
+ if (score.ScoreInfo.BeatmapInfo.RulesetID < 0 || score.ScoreInfo.BeatmapInfo.RulesetID > 3)
throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score));
}
@@ -44,7 +44,7 @@ namespace osu.Game.Scoring.Legacy
{
sw.Write((byte)(score.ScoreInfo.Ruleset.ID ?? 0));
sw.Write(LATEST_VERSION);
- sw.Write(score.ScoreInfo.Beatmap.MD5Hash);
+ sw.Write(score.ScoreInfo.BeatmapInfo.MD5Hash);
sw.Write(score.ScoreInfo.UserString);
sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash());
sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0));
diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs
index 890ead40e3..5cf22f7945 100644
--- a/osu.Game/Scoring/ScoreInfo.cs
+++ b/osu.Game/Scoring/ScoreInfo.cs
@@ -150,7 +150,8 @@ namespace osu.Game.Scoring
public int BeatmapInfoID { get; set; }
[JsonIgnore]
- public virtual BeatmapInfo Beatmap { get; set; }
+ [Column("Beatmap")]
+ public virtual BeatmapInfo BeatmapInfo { get; set; }
[JsonIgnore]
public long? OnlineScoreID { get; set; }
@@ -252,7 +253,7 @@ namespace osu.Game.Scoring
return clone;
}
- public override string ToString() => $"{User} playing {Beatmap}";
+ public override string ToString() => $"{User} playing {BeatmapInfo}";
public bool Equals(ScoreInfo other)
{
diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs
index d83b4e3f1d..dde956233b 100644
--- a/osu.Game/Scoring/ScoreManager.cs
+++ b/osu.Game/Scoring/ScoreManager.cs
@@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring;
namespace osu.Game.Scoring
{
- public class ScoreManager : IModelManager, IModelFileManager, IModelDownloader, ICanAcceptFiles, IPresentImports
+ public class ScoreManager : IModelManager, IModelFileManager, IModelDownloader, ICanAcceptFiles, IPostImports
{
private readonly Scheduler scheduler;
private readonly Func difficulties;
@@ -67,7 +67,7 @@ namespace osu.Game.Scoring
// Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below.
foreach (var s in scores)
{
- await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false);
+ await difficultyCache.GetDifficultyAsync(s.BeatmapInfo, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
}
}
@@ -126,7 +126,7 @@ namespace osu.Game.Scoring
/// The total score.
public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default)
{
- if (score.Beatmap == null)
+ if (score.BeatmapInfo == null)
return score.TotalScore;
int beatmapMaxCombo;
@@ -147,18 +147,18 @@ namespace osu.Game.Scoring
// This score is guaranteed to be an osu!stable score.
// The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used.
- if (score.Beatmap.MaxCombo != null)
- beatmapMaxCombo = score.Beatmap.MaxCombo.Value;
+ if (score.BeatmapInfo.MaxCombo != null)
+ beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value;
else
{
- if (score.Beatmap.ID == 0 || difficulties == null)
+ if (score.BeatmapInfo.ID == 0 || difficulties == null)
{
// We don't have enough information (max combo) to compute the score, so use the provided score.
return score.TotalScore;
}
// We can compute the max combo locally after the async beatmap difficulty computation.
- var difficulty = await difficulties().GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
+ var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false);
beatmapMaxCombo = difficulty.MaxCombo;
}
}
@@ -299,22 +299,22 @@ namespace osu.Game.Scoring
public IEnumerable HandledExtensions => scoreModelManager.HandledExtensions;
- public Task> Import(ProgressNotification notification, params ImportTask[] tasks)
+ public Task>> Import(ProgressNotification notification, params ImportTask[] tasks)
{
return scoreModelManager.Import(notification, tasks);
}
- public Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default)
{
return scoreModelManager.Import(task, lowPriority, cancellationToken);
}
- public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default)
{
return scoreModelManager.Import(archive, lowPriority, cancellationToken);
}
- public Task Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
+ public Task> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default)
{
return scoreModelManager.Import(item, archive, lowPriority, cancellationToken);
}
@@ -365,9 +365,9 @@ namespace osu.Game.Scoring
#region Implementation of IPresentImports
- public Action> PresentImport
+ public Action>> PostImport
{
- set => scoreModelManager.PresentImport = value;
+ set => scoreModelManager.PostImport = value;
}
#endregion
diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs
index bb15983de3..82685e9a04 100644
--- a/osu.Game/Scoring/ScorePerformanceCache.cs
+++ b/osu.Game/Scoring/ScorePerformanceCache.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Scoring
{
var score = lookup.ScoreInfo;
- var attributes = await difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, token).ConfigureAwait(false);
+ var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, token).ConfigureAwait(false);
// Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value.
if (attributes.Attributes == null)
diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs
index f5c5cd5dad..fd1f5ae3ec 100644
--- a/osu.Game/Scoring/ScoreStore.cs
+++ b/osu.Game/Scoring/ScoreStore.cs
@@ -17,9 +17,9 @@ namespace osu.Game.Scoring
protected override IQueryable AddIncludesForConsumption(IQueryable query)
=> base.AddIncludesForConsumption(query)
- .Include(s => s.Beatmap)
- .Include(s => s.Beatmap).ThenInclude(b => b.Metadata)
- .Include(s => s.Beatmap).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
+ .Include(s => s.BeatmapInfo)
+ .Include(s => s.BeatmapInfo).ThenInclude(b => b.Metadata)
+ .Include(s => s.BeatmapInfo).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
.Include(s => s.Ruleset);
}
}
diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs
index 5f9b72447b..c458b65607 100644
--- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs
+++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs
@@ -10,12 +10,12 @@ namespace osu.Game.Screens.Edit.Components.Menus
{
public class DifficultyMenuItem : StatefulMenuItem
{
- public BeatmapInfo Beatmap { get; }
+ public BeatmapInfo BeatmapInfo { get; }
public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc)
: base(beatmapInfo.Version ?? "(unnamed)", null)
{
- Beatmap = beatmapInfo;
+ BeatmapInfo = beatmapInfo;
State.Value = selected;
if (!selected)
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 2ff0101dc0..1170658abb 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -508,6 +508,7 @@ namespace osu.Game.Screens.Edit
if (isNewBeatmap || HasUnsavedChanges)
{
+ samplePlaybackDisabled.Value = true;
dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelExit));
return true;
}
@@ -756,7 +757,11 @@ namespace osu.Game.Screens.Edit
ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty
});
- private void cancelExit() => loader?.CancelPendingDifficultySwitch();
+ private void cancelExit()
+ {
+ samplePlaybackDisabled.Value = false;
+ loader?.CancelPendingDifficultySwitch();
+ }
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs
index 3402bf653a..64eb6225fa 100644
--- a/osu.Game/Screens/Edit/EditorBeatmap.cs
+++ b/osu.Game/Screens/Edit/EditorBeatmap.cs
@@ -86,6 +86,12 @@ namespace osu.Game.Screens.Edit
public BeatmapMetadata Metadata => PlayableBeatmap.Metadata;
+ public BeatmapDifficulty Difficulty
+ {
+ get => PlayableBeatmap.Difficulty;
+ set => PlayableBeatmap.Difficulty = value;
+ }
+
public ControlPointInfo ControlPointInfo
{
get => PlayableBeatmap.ControlPointInfo;
@@ -286,7 +292,7 @@ namespace osu.Game.Screens.Edit
///
public void Clear() => RemoveRange(HitObjects.ToArray());
- private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty);
+ private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, PlayableBeatmap.Difficulty);
private void trackStartTime(HitObject hitObject)
{
diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
index a8800d524f..75c6a89a66 100644
--- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs
+++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs
@@ -30,7 +30,7 @@ namespace osu.Game.Screens.Edit.Setup
Label = "Object Size",
FixedLabelWidth = LABEL_WIDTH,
Description = "The size of all hit objects",
- Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize)
+ Current = new BindableFloat(Beatmap.Difficulty.CircleSize)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Setup
Label = "Health Drain",
FixedLabelWidth = LABEL_WIDTH,
Description = "The rate of passive health drain throughout playable time",
- Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.DrainRate)
+ Current = new BindableFloat(Beatmap.Difficulty.DrainRate)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Setup
Label = "Approach Rate",
FixedLabelWidth = LABEL_WIDTH,
Description = "The speed at which objects are presented to the player",
- Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate)
+ Current = new BindableFloat(Beatmap.Difficulty.ApproachRate)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -69,7 +69,7 @@ namespace osu.Game.Screens.Edit.Setup
Label = "Overall Difficulty",
FixedLabelWidth = LABEL_WIDTH,
Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)",
- Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty)
+ Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty)
{
Default = BeatmapDifficulty.DEFAULT_DIFFICULTY,
MinValue = 0,
@@ -87,10 +87,10 @@ namespace osu.Game.Screens.Edit.Setup
{
// 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.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value;
- Beatmap.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value;
- Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value;
- Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
+ Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value;
+ Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value;
+ Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value;
+ Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value;
Beatmap.UpdateAllHitObjects();
}
diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
index fd43349793..f833bc49f7 100644
--- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
+++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs
@@ -89,6 +89,13 @@ namespace osu.Game.Screens.Edit.Setup
{
public Action OnFocused;
+ protected override bool OnDragStart(DragStartEvent e)
+ {
+ // This text box is intended to be "read only" without actually specifying that.
+ // As such we don't want to allow the user to select its content with a drag.
+ return false;
+ }
+
protected override void OnFocus(FocusEvent e)
{
OnFocused?.Invoke();
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index 0bfabdaa15..41097a4c74 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -49,14 +49,16 @@ namespace osu.Game.Screens
switch (introSequence)
{
case IntroSequence.Circles:
- return new IntroCircles();
+ return new IntroCircles(createMainMenu);
case IntroSequence.Welcome:
- return new IntroWelcome();
+ return new IntroWelcome(createMainMenu);
default:
- return new IntroTriangles();
+ return new IntroTriangles(createMainMenu);
}
+
+ MainMenu createMainMenu() => new MainMenu();
}
protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler();
diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs
index a1b8c3a203..2792d05f75 100644
--- a/osu.Game/Screens/Menu/IntroCircles.cs
+++ b/osu.Game/Screens/Menu/IntroCircles.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.Audio;
using osu.Framework.Audio.Sample;
@@ -20,6 +22,11 @@ namespace osu.Game.Screens.Menu
private Sample welcome;
+ public IntroCircles([CanBeNull] Func createNextScreen = null)
+ : base(createNextScreen)
+ {
+ }
+
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs
index cfe14eab92..32fb9f1d6d 100644
--- a/osu.Game/Screens/Menu/IntroScreen.cs
+++ b/osu.Game/Screens/Menu/IntroScreen.cs
@@ -1,15 +1,17 @@
// 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 JetBrains.Annotations;
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.Utils;
using osu.Framework.Screens;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.IO.Archives;
@@ -55,7 +57,7 @@ namespace osu.Game.Screens.Menu
private LeasedBindable beatmap;
- private MainMenu mainMenu;
+ private OsuScreen nextScreen;
[Resolved]
private AudioManager audio { get; set; }
@@ -63,12 +65,20 @@ namespace osu.Game.Screens.Menu
[Resolved]
private MusicController musicController { get; set; }
+ [CanBeNull]
+ private readonly Func createNextScreen;
+
///
/// Whether the is provided by osu! resources, rather than a user beatmap.
/// Only valid during or after .
///
protected bool UsingThemedIntro { get; private set; }
+ protected IntroScreen([CanBeNull] Func createNextScreen = null)
+ {
+ this.createNextScreen = createNextScreen;
+ }
+
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game)
{
@@ -101,8 +111,12 @@ namespace osu.Game.Screens.Menu
// if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state.
// this could happen if a user has nuked their files store. for now, reimport to repair this.
var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result;
- import.Protected = true;
- beatmaps.Update(import);
+
+ import.PerformWrite(b =>
+ {
+ b.Protected = true;
+ beatmaps.Update(b);
+ });
loadThemedIntro();
}
@@ -210,14 +224,21 @@ namespace osu.Game.Screens.Menu
}
}
- protected void PrepareMenuLoad() => LoadComponentAsync(mainMenu = new MainMenu());
+ protected void PrepareMenuLoad()
+ {
+ nextScreen = createNextScreen?.Invoke();
+
+ if (nextScreen != null)
+ LoadComponentAsync(nextScreen);
+ }
protected void LoadMenu()
{
beatmap.Return();
DidLoadMenu = true;
- this.Push(mainMenu);
+ if (nextScreen != null)
+ this.Push(nextScreen);
}
}
}
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
index a8ca17cec1..48ced63182 100644
--- a/osu.Game/Screens/Menu/IntroTriangles.cs
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@@ -44,6 +45,11 @@ namespace osu.Game.Screens.Menu
private DecoupleableInterpolatingFramedClock decoupledClock;
private TrianglesIntroSequence intro;
+ public IntroTriangles([CanBeNull] Func createNextScreen = null)
+ : base(createNextScreen)
+ {
+ }
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs
index f74043b045..639591cfef 100644
--- a/osu.Game/Screens/Menu/IntroWelcome.cs
+++ b/osu.Game/Screens/Menu/IntroWelcome.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 osuTK;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -32,6 +34,11 @@ namespace osu.Game.Screens.Menu
private BackgroundScreenDefault background;
+ public IntroWelcome([CanBeNull] Func createNextScreen = null)
+ : base(createNextScreen)
+ {
+ }
+
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
index a64d89b699..381849189d 100644
--- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs
@@ -66,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
req.Failure += exception =>
{
- onError?.Invoke(req.Result?.Error ?? exception.Message);
+ onError?.Invoke(req.Response?.Error ?? exception.Message);
};
api.Queue(req);
diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
index 69eb857661..585b024623 100644
--- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
+++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs
@@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay
difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) };
beatmapText.Clear();
- beatmapText.AddLink(Item.Beatmap.Value.ToRomanisableString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text =>
+ beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text =>
{
text.Truncate = true;
text.RelativeSizeAxes = Axes.X;
diff --git a/osu.Game/Screens/OnlinePlay/Header.cs b/osu.Game/Screens/OnlinePlay/Header.cs
index b0db9256f5..2d4b5cc527 100644
--- a/osu.Game/Screens/OnlinePlay/Header.cs
+++ b/osu.Game/Screens/OnlinePlay/Header.cs
@@ -72,21 +72,21 @@ namespace osu.Game.Screens.OnlinePlay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(size: 24),
+ Font = OsuFont.TorusAlternate.With(size: 24),
Text = mainTitle
},
dot = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(size: 24),
+ Font = OsuFont.TorusAlternate.With(size: 24),
Text = "·"
},
pageTitle = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- Font = OsuFont.GetFont(size: 24),
+ Font = OsuFont.TorusAlternate.With(size: 24),
Text = "Lounge"
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index 03d13c353a..acd87ed864 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -379,7 +379,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
if (item.NewValue?.Beatmap.Value != null)
{
statusText.Text = "Currently playing ";
- beatmapText.AddLink(item.NewValue.Beatmap.Value.ToRomanisableString(),
+ beatmapText.AddLink(item.NewValue.Beatmap.Value.GetDisplayTitleRomanisable(),
LinkAction.OpenBeatmap,
item.NewValue.Beatmap.Value.OnlineBeatmapID.ToString(),
creationParameters: s =>
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
index c45e3a79da..7bf8ce0e1a 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
@@ -213,8 +213,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{
}
- protected override void StartGameplay(int userId, GameplayState gameplayState)
- => instances.Single(i => i.UserId == userId).LoadScore(gameplayState.Score);
+ protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState)
+ => instances.Single(i => i.UserId == userId).LoadScore(spectatorGameplayState.Score);
protected override void EndGameplay(int userId)
{
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs
index 95ccc08608..c3190cd845 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
Score = score;
- gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods)
+ gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods)
{
RelativeSizeAxes = Axes.Both,
Child = stack = new OsuScreenStack()
diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs
deleted file mode 100644
index 74fbe540fa..0000000000
--- a/osu.Game/Screens/Play/GameplayBeatmap.cs
+++ /dev/null
@@ -1,56 +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.Collections.Generic;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Beatmaps.Timing;
-using osu.Game.Rulesets.Judgements;
-using osu.Game.Rulesets.Objects;
-
-namespace osu.Game.Screens.Play
-{
- public class GameplayBeatmap : Component, IBeatmap
- {
- public readonly IBeatmap PlayableBeatmap;
-
- public GameplayBeatmap(IBeatmap playableBeatmap)
- {
- PlayableBeatmap = playableBeatmap;
- }
-
- public BeatmapInfo BeatmapInfo
- {
- get => PlayableBeatmap.BeatmapInfo;
- set => PlayableBeatmap.BeatmapInfo = value;
- }
-
- public BeatmapMetadata Metadata => PlayableBeatmap.Metadata;
-
- public ControlPointInfo ControlPointInfo
- {
- get => PlayableBeatmap.ControlPointInfo;
- set => PlayableBeatmap.ControlPointInfo = value;
- }
-
- public List Breaks => PlayableBeatmap.Breaks;
-
- public double TotalBreakTime => PlayableBeatmap.TotalBreakTime;
-
- public IReadOnlyList HitObjects => PlayableBeatmap.HitObjects;
-
- public IEnumerable GetStatistics() => PlayableBeatmap.GetStatistics();
-
- public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength();
-
- public IBeatmap Clone() => PlayableBeatmap.Clone();
-
- private readonly Bindable lastJudgementResult = new Bindable();
-
- public IBindable LastJudgementResult => lastJudgementResult;
-
- public void ApplyResult(JudgementResult result) => lastJudgementResult.Value = result;
- }
-}
diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs
new file mode 100644
index 0000000000..44f72022f7
--- /dev/null
+++ b/osu.Game/Screens/Play/GameplayState.cs
@@ -0,0 +1,63 @@
+// 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.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+
+#nullable enable
+
+namespace osu.Game.Screens.Play
+{
+ ///
+ /// The state of an active gameplay session, generally constructed and exposed by .
+ ///
+ public class GameplayState
+ {
+ ///
+ /// The final post-convert post-mod-application beatmap.
+ ///
+ public readonly IBeatmap Beatmap;
+
+ ///
+ /// The ruleset used in gameplay.
+ ///
+ public readonly Ruleset Ruleset;
+
+ ///
+ /// The mods applied to the gameplay.
+ ///
+ public readonly IReadOnlyList Mods;
+
+ ///
+ /// The gameplay score.
+ ///
+ public readonly Score Score;
+
+ ///
+ /// A bindable tracking the last judgement result applied to any hit object.
+ ///
+ public IBindable LastJudgementResult => lastJudgementResult;
+
+ private readonly Bindable lastJudgementResult = new Bindable();
+
+ public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null)
+ {
+ Beatmap = beatmap;
+ Ruleset = ruleset;
+ Score = score ?? new Score();
+ Mods = mods ?? ArraySegment.Empty;
+ }
+
+ ///
+ /// Applies the score change of a to this .
+ ///
+ /// The to apply.
+ public void ApplyResult(JudgementResult result) => lastJudgementResult.Value = result;
+ }
+}
diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs
new file mode 100644
index 0000000000..ef289c2a20
--- /dev/null
+++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs
@@ -0,0 +1,227 @@
+// 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.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Localisation;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Screens.Play.HUD
+{
+ public class PerformancePointsCounter : RollingCounter, ISkinnableDrawable
+ {
+ public bool UsesFixedAnchor { get; set; }
+
+ protected override bool IsRollingProportional => true;
+
+ protected override double RollingDuration => 1000;
+
+ private const float alpha_when_invalid = 0.3f;
+
+ [CanBeNull]
+ [Resolved(CanBeNull = true)]
+ private ScoreProcessor scoreProcessor { get; set; }
+
+ [Resolved(CanBeNull = true)]
+ [CanBeNull]
+ private GameplayState gameplayState { get; set; }
+
+ [CanBeNull]
+ private List timedAttributes;
+
+ private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource();
+
+ private JudgementResult lastJudgement;
+
+ public PerformancePointsCounter()
+ {
+ Current.Value = DisplayedCount = 0;
+ }
+
+ private Mod[] clonedMods;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache)
+ {
+ Colour = colours.BlueLighter;
+
+ if (gameplayState != null)
+ {
+ clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray();
+
+ var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap);
+ difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token)
+ .ContinueWith(r => Schedule(() =>
+ {
+ timedAttributes = r.Result;
+ IsValid = true;
+ if (lastJudgement != null)
+ onJudgementChanged(lastJudgement);
+ }), TaskContinuationOptions.OnlyOnRanToCompletion);
+ }
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (scoreProcessor != null)
+ {
+ scoreProcessor.NewJudgement += onJudgementChanged;
+ scoreProcessor.JudgementReverted += onJudgementChanged;
+ }
+ }
+
+ private bool isValid;
+
+ protected bool IsValid
+ {
+ set
+ {
+ if (value == isValid)
+ return;
+
+ isValid = value;
+ DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint);
+ }
+ }
+
+ private void onJudgementChanged(JudgementResult judgement)
+ {
+ lastJudgement = judgement;
+
+ var attrib = getAttributeAtTime(judgement);
+
+ if (gameplayState == null || attrib == null)
+ {
+ IsValid = false;
+ return;
+ }
+
+ // awkward but we need to make sure the true mods are not passed to PerformanceCalculator as it makes a mess of track applications.
+ var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone();
+ scoreInfo.Mods = clonedMods;
+
+ var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, scoreInfo);
+
+ Current.Value = (int)Math.Round(calculator?.Calculate() ?? 0, MidpointRounding.AwayFromZero);
+ IsValid = true;
+ }
+
+ [CanBeNull]
+ private DifficultyAttributes getAttributeAtTime(JudgementResult judgement)
+ {
+ if (timedAttributes == null || timedAttributes.Count == 0)
+ return null;
+
+ int attribIndex = timedAttributes.BinarySearch(new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null));
+ if (attribIndex < 0)
+ attribIndex = ~attribIndex - 1;
+
+ return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes;
+ }
+
+ protected override LocalisableString FormatCount(int count) => count.ToString(@"D");
+
+ protected override IHasText CreateText() => new TextComponent
+ {
+ Alpha = alpha_when_invalid
+ };
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (scoreProcessor != null)
+ scoreProcessor.NewJudgement -= onJudgementChanged;
+
+ loadCancellationSource?.Cancel();
+ }
+
+ private class TextComponent : CompositeDrawable, IHasText
+ {
+ public LocalisableString Text
+ {
+ get => text.Text;
+ set => text.Text = value;
+ }
+
+ private readonly OsuSpriteText text;
+
+ public TextComponent()
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(2),
+ Children = new Drawable[]
+ {
+ text = new OsuSpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Font = OsuFont.Numeric.With(size: 16)
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Text = @"pp",
+ Font = OsuFont.Numeric.With(size: 8),
+ Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
+ }
+ }
+ };
+ }
+ }
+
+ // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap.
+ private class GameplayWorkingBeatmap : WorkingBeatmap
+ {
+ private readonly IBeatmap gameplayBeatmap;
+
+ public GameplayWorkingBeatmap(IBeatmap gameplayBeatmap)
+ : base(gameplayBeatmap.BeatmapInfo, null)
+ {
+ this.gameplayBeatmap = gameplayBeatmap;
+ }
+
+ public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null)
+ => gameplayBeatmap;
+
+ protected override IBeatmap GetBeatmap() => gameplayBeatmap;
+
+ protected override Texture GetBackground() => throw new NotImplementedException();
+
+ protected override Track GetBeatmapTrack() => throw new NotImplementedException();
+
+ protected internal override ISkin GetSkin() => throw new NotImplementedException();
+
+ public override Stream GetStream(string storagePath) => throw new NotImplementedException();
+ }
+ }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 9927467bd6..30ba2bfe6d 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -2,7 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -16,6 +15,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Threading;
+using osu.Game.Audio.Effects;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
@@ -93,9 +93,9 @@ namespace osu.Game.Screens.Play
[Resolved]
private SpectatorClient spectatorClient { get; set; }
- protected Ruleset GameplayRuleset { get; private set; }
+ public GameplayState GameplayState { get; private set; }
- protected GameplayBeatmap GameplayBeatmap { get; private set; }
+ private Ruleset ruleset;
private Sample sampleRestart;
@@ -125,15 +125,11 @@ namespace osu.Game.Screens.Play
public DimmableStoryboard DimmableStoryboard { get; private set; }
- [Cached]
- [Cached(Type = typeof(IBindable>))]
- protected new readonly Bindable> Mods = new Bindable>(Array.Empty());
-
///
/// Whether failing should be allowed.
/// By default, this checks whether all selected mods allow failing.
///
- protected virtual bool CheckModsAllowFailure() => Mods.Value.OfType().All(m => m.PerformFail());
+ protected virtual bool CheckModsAllowFailure() => GameplayState.Mods.OfType().All(m => m.PerformFail());
public readonly PlayerConfiguration Configuration;
@@ -161,13 +157,6 @@ namespace osu.Game.Screens.Play
if (!LoadedBeatmapSuccessfully)
return;
- Score = CreateScore();
-
- // ensure the score is in a consistent state with the current player.
- Score.ScoreInfo.Beatmap = Beatmap.Value.BeatmapInfo;
- Score.ScoreInfo.Ruleset = GameplayRuleset.RulesetInfo;
- Score.ScoreInfo.Mods = Mods.Value.ToArray();
-
PrepareReplay();
ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo);
@@ -186,12 +175,12 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game)
{
- Mods.Value = base.Mods.Value.Select(m => m.DeepClone()).ToArray();
+ var gameplayMods = Mods.Value.Select(m => m.DeepClone()).ToArray();
if (Beatmap.Value is DummyWorkingBeatmap)
return;
- IBeatmap playableBeatmap = loadPlayableBeatmap();
+ IBeatmap playableBeatmap = loadPlayableBeatmap(gameplayMods);
if (playableBeatmap == null)
return;
@@ -206,16 +195,16 @@ namespace osu.Game.Screens.Play
if (game is OsuGame osuGame)
LocalUserPlaying.BindTo(osuGame.LocalUserPlaying);
- DrawableRuleset = GameplayRuleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
+ DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods);
dependencies.CacheAs(DrawableRuleset);
- ScoreProcessor = GameplayRuleset.CreateScoreProcessor();
+ ScoreProcessor = ruleset.CreateScoreProcessor();
ScoreProcessor.ApplyBeatmap(playableBeatmap);
- ScoreProcessor.Mods.BindTo(Mods);
+ ScoreProcessor.Mods.Value = gameplayMods;
dependencies.CacheAs(ScoreProcessor);
- HealthProcessor = GameplayRuleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
+ HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime);
HealthProcessor.ApplyBeatmap(playableBeatmap);
dependencies.CacheAs(HealthProcessor);
@@ -225,12 +214,19 @@ namespace osu.Game.Screens.Play
InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime);
- AddInternal(GameplayBeatmap = new GameplayBeatmap(playableBeatmap));
AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer));
+ AddInternal(failLowPassFilter = new AudioFilter(audio.TrackMixer));
- dependencies.CacheAs(GameplayBeatmap);
+ Score = CreateScore(playableBeatmap);
- var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin);
+ // ensure the score is in a consistent state with the current player.
+ Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo;
+ Score.ScoreInfo.Ruleset = ruleset.RulesetInfo;
+ Score.ScoreInfo.Mods = gameplayMods;
+
+ dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score));
+
+ var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin);
// load the skinning hierarchy first.
// this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources.
@@ -280,7 +276,7 @@ namespace osu.Game.Screens.Play
{
HealthProcessor.ApplyResult(r);
ScoreProcessor.ApplyResult(r);
- GameplayBeatmap.ApplyResult(r);
+ GameplayState.ApplyResult(r);
};
DrawableRuleset.RevertResult += r =>
@@ -303,13 +299,13 @@ namespace osu.Game.Screens.Play
// this is required for mods that apply transforms to these processors.
ScoreProcessor.OnLoadComplete += _ =>
{
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in gameplayMods.OfType())
mod.ApplyToScoreProcessor(ScoreProcessor);
};
HealthProcessor.OnLoadComplete += _ =>
{
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in gameplayMods.OfType())
mod.ApplyToHealthProcessor(HealthProcessor);
};
@@ -357,7 +353,7 @@ namespace osu.Game.Screens.Play
// display the cursor above some HUD elements.
DrawableRuleset.Cursor?.CreateProxy() ?? new Container(),
DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(),
- HUDOverlay = new HUDOverlay(DrawableRuleset, Mods.Value)
+ HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods)
{
HoldToQuit =
{
@@ -468,7 +464,7 @@ namespace osu.Game.Screens.Play
}
}
- private IBeatmap loadPlayableBeatmap()
+ private IBeatmap loadPlayableBeatmap(Mod[] gameplayMods)
{
IBeatmap playable;
@@ -478,19 +474,19 @@ namespace osu.Game.Screens.Play
throw new InvalidOperationException("Beatmap was not loaded");
var rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset;
- GameplayRuleset = rulesetInfo.CreateInstance();
+ ruleset = rulesetInfo.CreateInstance();
try
{
- playable = Beatmap.Value.GetPlayableBeatmap(GameplayRuleset.RulesetInfo, Mods.Value);
+ playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, gameplayMods);
}
catch (BeatmapInvalidForRulesetException)
{
// A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset
rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset;
- GameplayRuleset = rulesetInfo.CreateInstance();
+ ruleset = rulesetInfo.CreateInstance();
- playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, Mods.Value);
+ playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, gameplayMods);
}
if (playable.HitObjects.Count == 0)
@@ -774,6 +770,8 @@ namespace osu.Game.Screens.Play
private FailAnimation failAnimation;
+ private AudioFilter failLowPassFilter;
+
private bool onFail()
{
if (!CheckModsAllowFailure())
@@ -788,9 +786,10 @@ namespace osu.Game.Screens.Play
if (PauseOverlay.State.Value == Visibility.Visible)
PauseOverlay.Hide();
+ failLowPassFilter.CutoffTo(300, 2500, Easing.OutCubic);
failAnimation.Start();
- if (Mods.Value.OfType().Any(m => m.RestartOnFail))
+ if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail))
Restart();
return true;
@@ -800,6 +799,7 @@ namespace osu.Game.Screens.Play
private void onFailComplete()
{
GameplayClockContainer.Stop();
+ failLowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
FailOverlay.Retries = RestartCount;
FailOverlay.Show();
@@ -920,17 +920,17 @@ namespace osu.Game.Screens.Play
storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable;
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in GameplayState.Mods.OfType())
mod.ApplyToPlayer(this);
- foreach (var mod in Mods.Value.OfType())
+ foreach (var mod in GameplayState.Mods.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())
+ foreach (var mod in GameplayState.Mods.OfType())
mod.ApplyToTrack(musicController.CurrentTrack);
updateGameplayState();
@@ -989,8 +989,9 @@ namespace osu.Game.Screens.Play
///
/// Creates the player's .
///
+ ///
/// The .
- protected virtual Score CreateScore() => new Score
+ protected virtual Score CreateScore(IBeatmap beatmap) => new Score
{
ScoreInfo = new ScoreInfo { User = api.LocalUser.Value },
};
@@ -1010,7 +1011,7 @@ namespace osu.Game.Screens.Play
using (var stream = new MemoryStream())
{
- new LegacyScoreEncoder(score, GameplayBeatmap.PlayableBeatmap).Encode(stream);
+ new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream);
replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr");
}
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 969527a758..94a61a4ef3 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Framework.Screens;
using osu.Framework.Threading;
+using osu.Game.Audio.Effects;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
@@ -63,6 +64,8 @@ namespace osu.Game.Screens.Play
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
+ private AudioFilter lowPassFilter;
+
protected bool BackgroundBrightnessReduction
{
set
@@ -127,7 +130,7 @@ namespace osu.Game.Screens.Play
}
[BackgroundDependencyLoader]
- private void load(SessionStatics sessionStatics)
+ private void load(SessionStatics sessionStatics, AudioManager audio)
{
muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce);
batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce);
@@ -159,7 +162,8 @@ namespace osu.Game.Screens.Play
new InputSettings()
}
},
- idleTracker = new IdleTracker(750)
+ idleTracker = new IdleTracker(750),
+ lowPassFilter = new AudioFilter(audio.TrackMixer)
});
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
@@ -191,6 +195,7 @@ namespace osu.Game.Screens.Play
epilepsyWarning.DimmableBackground = b;
});
+ lowPassFilter.CutoffTo(500, 100, Easing.OutCubic);
Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment);
content.ScaleTo(0.7f);
@@ -229,6 +234,7 @@ namespace osu.Game.Screens.Play
// stop the track before removing adjustment to avoid a volume spike.
Beatmap.Value.Track.Stop();
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
+ lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
}
public override bool OnExiting(IScreen next)
@@ -242,6 +248,7 @@ namespace osu.Game.Screens.Play
BackgroundBrightnessReduction = false;
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
+ lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
return base.OnExiting(next);
}
diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs
index 0c6f1ed911..93054b7bb5 100644
--- a/osu.Game/Screens/Play/ReplayPlayer.cs
+++ b/osu.Game/Screens/Play/ReplayPlayer.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play
DrawableRuleset?.SetReplayScore(Score);
}
- protected override Score CreateScore() => createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value);
+ protected override Score CreateScore(IBeatmap beatmap) => createScore(beatmap, Mods.Value);
// Don't re-import replay scores as they're already present in the database.
protected override Task ImportScore(Score score) => Task.CompletedTask;
@@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play
void keyboardSeek(int direction)
{
- double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayBeatmap.HitObjects.Last().GetEndTime());
+ double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayState.Beatmap.HitObjects.Last().GetEndTime());
Seek(target);
}
diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs
index d90e8e0168..675cb71311 100644
--- a/osu.Game/Screens/Play/SoloPlayer.cs
+++ b/osu.Game/Screens/Play/SoloPlayer.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play
protected override APIRequest CreateSubmissionRequest(Score score, long token)
{
- var beatmap = score.ScoreInfo.Beatmap;
+ var beatmap = score.ScoreInfo.BeatmapInfo;
Debug.Assert(beatmap.OnlineBeatmapID != null);
diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs
index 4520e2e825..9d4dad8bdc 100644
--- a/osu.Game/Screens/Play/SoloSpectator.cs
+++ b/osu.Game/Screens/Play/SoloSpectator.cs
@@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play
/// The player's immediate online gameplay state.
/// This doesn't always reflect the gameplay state being watched.
///
- private GameplayState immediateGameplayState;
+ private SpectatorGameplayState immediateSpectatorGameplayState;
private GetBeatmapSetRequest onlineBeatmapRequest;
@@ -146,7 +146,7 @@ namespace osu.Game.Screens.Play
Width = 250,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
- Action = () => scheduleStart(immediateGameplayState),
+ Action = () => scheduleStart(immediateSpectatorGameplayState),
Enabled = { Value = false }
}
}
@@ -167,18 +167,18 @@ namespace osu.Game.Screens.Play
showBeatmapPanel(spectatorState);
}
- protected override void StartGameplay(int userId, GameplayState gameplayState)
+ protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState)
{
- immediateGameplayState = gameplayState;
+ immediateSpectatorGameplayState = spectatorGameplayState;
watchButton.Enabled.Value = true;
- scheduleStart(gameplayState);
+ scheduleStart(spectatorGameplayState);
}
protected override void EndGameplay(int userId)
{
scheduledStart?.Cancel();
- immediateGameplayState = null;
+ immediateSpectatorGameplayState = null;
watchButton.Enabled.Value = false;
clearDisplay();
@@ -194,7 +194,7 @@ namespace osu.Game.Screens.Play
private ScheduledDelegate scheduledStart;
- private void scheduleStart(GameplayState gameplayState)
+ private void scheduleStart(SpectatorGameplayState spectatorGameplayState)
{
// This function may be called multiple times in quick succession once the screen becomes current again.
scheduledStart?.Cancel();
@@ -203,15 +203,15 @@ namespace osu.Game.Screens.Play
if (this.IsCurrentScreen())
start();
else
- scheduleStart(gameplayState);
+ scheduleStart(spectatorGameplayState);
});
void start()
{
- Beatmap.Value = gameplayState.Beatmap;
- Ruleset.Value = gameplayState.Ruleset.RulesetInfo;
+ Beatmap.Value = spectatorGameplayState.Beatmap;
+ Ruleset.Value = spectatorGameplayState.Ruleset.RulesetInfo;
- this.Push(new SpectatorPlayerLoader(gameplayState.Score, () => new SoloSpectatorPlayer(gameplayState.Score)));
+ this.Push(new SpectatorPlayerLoader(spectatorGameplayState.Score, () => new SoloSpectatorPlayer(spectatorGameplayState.Score)));
}
}
diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs
index d7e42a9cd1..f6a89e7fa9 100644
--- a/osu.Game/Screens/Play/SpectatorPlayer.cs
+++ b/osu.Game/Screens/Play/SpectatorPlayer.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
+using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Spectator;
@@ -66,8 +67,8 @@ namespace osu.Game.Screens.Play
foreach (var frame in bundle.Frames)
{
- IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame();
- convertibleFrame.FromLegacy(frame, GameplayBeatmap.PlayableBeatmap);
+ IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame();
+ convertibleFrame.FromLegacy(frame, GameplayState.Beatmap);
var convertedFrame = (ReplayFrame)convertibleFrame;
convertedFrame.Time = frame.Time;
@@ -79,7 +80,7 @@ namespace osu.Game.Screens.Play
NonFrameStableSeek(score.Replay.Frames[0].Time);
}
- protected override Score CreateScore() => score;
+ protected override Score CreateScore(IBeatmap beatmap) => score;
protected override ResultsScreen CreateResults(ScoreInfo score)
=> new SpectatorResultsScreen(score);
diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
index bcb5e7999f..262d1e8293 100644
--- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
+++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs
@@ -60,7 +60,7 @@ namespace osu.Game.Screens.Ranking.Expanded
[BackgroundDependencyLoader]
private void load(BeatmapDifficultyCache beatmapDifficultyCache)
{
- var beatmap = score.Beatmap;
+ var beatmap = score.BeatmapInfo;
var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata;
var creator = metadata.Author?.Username;
diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
index d96b6989b4..e644eb671a 100644
--- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
+++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Screens.Ranking
}
[BackgroundDependencyLoader(true)]
- private void load(OsuGame game, ScoreModelDownloader scores)
+ private void load(OsuGame game, ScoreManager scores)
{
InternalChild = shakeContainer = new ShakeContainer
{
@@ -60,7 +60,7 @@ namespace osu.Game.Screens.Ranking
break;
case DownloadState.NotDownloaded:
- scores.Download(Model.Value);
+ scores.Download(Model.Value, false);
break;
case DownloadState.Importing:
diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs
index 9bc696948f..5e582a8dcb 100644
--- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs
+++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs
@@ -27,10 +27,10 @@ namespace osu.Game.Screens.Ranking
protected override APIRequest FetchScores(Action> scoresCallback)
{
- if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending)
+ if (Score.BeatmapInfo.OnlineBeatmapID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending)
return null;
- getScoreRequest = new GetScoresRequest(Score.Beatmap, Score.Ruleset);
+ getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset);
getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets)));
return getScoreRequest;
}
diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs
index f1ae1f9d73..bc62bcf2b2 100644
--- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs
+++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs
@@ -102,7 +102,7 @@ namespace osu.Game.Screens.Ranking.Statistics
// Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events.
Task.Run(() =>
{
- playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.Beatmap).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty());
+ playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty());
}, loadCancellation.Token).ContinueWith(t => Schedule(() =>
{
var rows = new FillFlowContainer
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs
index 5eceae3c6e..e5e28d2fde 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select
///
/// The currently selected beatmap.
///
- public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap;
+ public BeatmapInfo SelectedBeatmapInfo => selectedBeatmap?.BeatmapInfo;
private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected);
@@ -65,7 +65,7 @@ namespace osu.Game.Screens.Select
private CarouselBeatmapSet selectedBeatmapSet;
///
- /// Raised when the is changed.
+ /// Raised when the is changed.
///
public Action SelectionChanged;
@@ -212,7 +212,7 @@ namespace osu.Game.Screens.Select
// If the selected beatmap is about to be removed, store its ID so it can be re-selected if required
if (existingSet?.State?.Value == CarouselItemState.Selected)
- previouslySelectedID = selectedBeatmap?.Beatmap.ID;
+ previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID;
var newSet = createCarouselSet(beatmapSet);
@@ -233,7 +233,7 @@ namespace osu.Game.Screens.Select
// check if we can/need to maintain our current selection.
if (previouslySelectedID != null)
- select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == previouslySelectedID) ?? newSet);
+ select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet);
itemsCache.Invalidate();
Schedule(() => BeatmapSetsChanged?.Invoke());
@@ -242,15 +242,15 @@ namespace osu.Game.Screens.Select
///
/// Selects a given beatmap on the carousel.
///
- /// The beatmap to select.
+ /// The beatmap to select.
/// Whether to select the beatmap even if it is filtered (i.e., not visible on carousel).
/// True if a selection was made, False if it wasn't.
- public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true)
+ public bool SelectBeatmap(BeatmapInfo beatmapInfo, bool bypassFilters = true)
{
// ensure that any pending events from BeatmapManager have been run before attempting a selection.
Scheduler.Update();
- if (beatmap?.Hidden != false)
+ if (beatmapInfo?.Hidden != false)
return false;
foreach (CarouselBeatmapSet set in beatmapSets)
@@ -258,7 +258,7 @@ namespace osu.Game.Screens.Select
if (!bypassFilters && set.Filtered.Value)
continue;
- var item = set.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap));
+ var item = set.Beatmaps.FirstOrDefault(p => p.BeatmapInfo.Equals(beatmapInfo));
if (item == null)
// The beatmap that needs to be selected doesn't exist in this set
@@ -472,7 +472,7 @@ namespace osu.Game.Screens.Select
private float? scrollTarget;
///
- /// Scroll to the current .
+ /// Scroll to the current .
///
///
/// Whether the scroll position should immediately be shifted to the target, delegating animation to visible panels.
@@ -720,7 +720,7 @@ namespace osu.Game.Screens.Select
if (state.NewValue == CarouselItemState.Selected)
{
selectedBeatmapSet = set;
- SelectionChanged?.Invoke(c.Beatmap);
+ SelectionChanged?.Invoke(c.BeatmapInfo);
itemsCache.Invalidate();
ScrollToSelected();
diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs
index b32416b361..4970db8955 100644
--- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs
+++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs
@@ -17,9 +17,9 @@ namespace osu.Game.Screens.Select
[Resolved]
private ScoreManager scoreManager { get; set; }
- public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion)
+ public BeatmapClearScoresDialog(BeatmapInfo beatmapInfo, Action onCompletion)
{
- BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}";
+ BodyText = $@"{beatmapInfo.Metadata?.Artist} - {beatmapInfo.Metadata?.Title}";
Icon = FontAwesome.Solid.Eraser;
HeaderText = @"Clearing all local scores. Are you sure?";
Buttons = new PopupDialogButton[]
@@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select
Text = @"Yes. Please.",
Action = () =>
{
- Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == beatmap.ID).ToList()))
+ Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == beatmapInfo.ID).ToList()))
.ContinueWith(_ => onCompletion);
}
},
diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs
index 89ae92ec91..72c2ba708b 100644
--- a/osu.Game/Screens/Select/BeatmapDetailArea.cs
+++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs
@@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select
{
beatmap = value;
- Details.Beatmap = value?.BeatmapInfo;
+ Details.BeatmapInfo = value?.BeatmapInfo;
}
}
diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs
index 973f54c038..6ace92370c 100644
--- a/osu.Game/Screens/Select/BeatmapDetails.cs
+++ b/osu.Game/Screens/Select/BeatmapDetails.cs
@@ -41,16 +41,16 @@ namespace osu.Game.Screens.Select
[Resolved]
private RulesetStore rulesets { get; set; }
- private BeatmapInfo beatmap;
+ private BeatmapInfo beatmapInfo;
- public BeatmapInfo Beatmap
+ public BeatmapInfo BeatmapInfo
{
- get => beatmap;
+ get => beatmapInfo;
set
{
- if (value == beatmap) return;
+ if (value == beatmapInfo) return;
- beatmap = value;
+ beatmapInfo = value;
Scheduler.AddOnce(updateStatistics);
}
@@ -170,26 +170,26 @@ namespace osu.Game.Screens.Select
private void updateStatistics()
{
- advanced.Beatmap = Beatmap;
- description.Text = Beatmap?.Version;
- source.Text = Beatmap?.Metadata?.Source;
- tags.Text = Beatmap?.Metadata?.Tags;
+ advanced.BeatmapInfo = BeatmapInfo;
+ description.Text = BeatmapInfo?.Version;
+ source.Text = BeatmapInfo?.Metadata?.Source;
+ tags.Text = BeatmapInfo?.Metadata?.Tags;
// metrics may have been previously fetched
- if (Beatmap?.BeatmapSet?.Metrics != null && Beatmap?.Metrics != null)
+ if (BeatmapInfo?.BeatmapSet?.Metrics != null && BeatmapInfo?.Metrics != null)
{
updateMetrics();
return;
}
// for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time).
- if (Beatmap?.OnlineBeatmapID == null || api.State.Value == APIState.Offline)
+ if (BeatmapInfo?.OnlineBeatmapID == null || api.State.Value == APIState.Offline)
{
updateMetrics();
return;
}
- var requestedBeatmap = Beatmap;
+ var requestedBeatmap = BeatmapInfo;
var lookup = new GetBeatmapRequest(requestedBeatmap);
@@ -197,11 +197,11 @@ namespace osu.Game.Screens.Select
{
Schedule(() =>
{
- if (beatmap != requestedBeatmap)
+ if (beatmapInfo != requestedBeatmap)
// the beatmap has been changed since we started the lookup.
return;
- var b = res.ToBeatmap(rulesets);
+ var b = res.ToBeatmapInfo(rulesets);
if (requestedBeatmap.BeatmapSet == null)
requestedBeatmap.BeatmapSet = b.BeatmapSet;
@@ -218,7 +218,7 @@ namespace osu.Game.Screens.Select
{
Schedule(() =>
{
- if (beatmap != requestedBeatmap)
+ if (beatmapInfo != requestedBeatmap)
// the beatmap has been changed since we started the lookup.
return;
@@ -232,12 +232,12 @@ namespace osu.Game.Screens.Select
private void updateMetrics()
{
- var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false;
- var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false);
+ var hasRatings = beatmapInfo?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false;
+ var hasRetriesFails = (beatmapInfo?.Metrics?.Retries?.Any() ?? false) || (beatmapInfo?.Metrics?.Fails?.Any() ?? false);
if (hasRatings)
{
- ratings.Metrics = beatmap.BeatmapSet.Metrics;
+ ratings.Metrics = beatmapInfo.BeatmapSet.Metrics;
ratings.FadeIn(transition_duration);
}
else
@@ -249,7 +249,7 @@ namespace osu.Game.Screens.Select
if (hasRetriesFails)
{
- failRetryGraph.Metrics = beatmap.Metrics;
+ failRetryGraph.Metrics = beatmapInfo.Metrics;
failRetryContainer.FadeIn(transition_duration);
}
else
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
index f95ddfee41..d8c5aa760e 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
@@ -12,11 +12,11 @@ namespace osu.Game.Screens.Select.Carousel
{
public override float TotalHeight => DrawableCarouselBeatmap.HEIGHT;
- public readonly BeatmapInfo Beatmap;
+ public readonly BeatmapInfo BeatmapInfo;
- public CarouselBeatmap(BeatmapInfo beatmap)
+ public CarouselBeatmap(BeatmapInfo beatmapInfo)
{
- Beatmap = beatmap;
+ BeatmapInfo = beatmapInfo;
State.Value = CarouselItemState.Collapsed;
}
@@ -28,36 +28,36 @@ namespace osu.Game.Screens.Select.Carousel
bool match =
criteria.Ruleset == null ||
- Beatmap.RulesetID == criteria.Ruleset.ID ||
- (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
+ BeatmapInfo.RulesetID == criteria.Ruleset.ID ||
+ (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps);
- if (Beatmap.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true)
+ if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true)
{
// only check ruleset equality or convertability for selected beatmap
Filtered.Value = !match;
return;
}
- match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty);
- match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate);
- match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate);
- match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize);
- match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(Beatmap.BaseDifficulty.OverallDifficulty);
- match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length);
- match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM);
+ match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarDifficulty);
+ match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.BaseDifficulty.ApproachRate);
+ match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.BaseDifficulty.DrainRate);
+ match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.BaseDifficulty.CircleSize);
+ match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.BaseDifficulty.OverallDifficulty);
+ match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length);
+ match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM);
- match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor);
- match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(Beatmap.Status);
+ match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor);
+ match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(BeatmapInfo.Status);
- match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(Beatmap.Metadata.AuthorString);
- match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(Beatmap.Metadata.Artist) ||
- criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode);
+ match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(BeatmapInfo.Metadata.AuthorString);
+ match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) ||
+ criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode);
- match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(Beatmap.StarDifficulty);
+ match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarDifficulty);
if (match)
{
- var terms = Beatmap.SearchableTerms;
+ var terms = BeatmapInfo.GetSearchableTerms();
foreach (var criteriaTerm in criteria.SearchTerms)
match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase));
@@ -66,16 +66,16 @@ namespace osu.Game.Screens.Select.Carousel
// this should be done after text matching so we can prioritise matching numbers in metadata.
if (!match && criteria.SearchNumber.HasValue)
{
- match = (Beatmap.OnlineBeatmapID == criteria.SearchNumber.Value) ||
- (Beatmap.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value);
+ match = (BeatmapInfo.OnlineBeatmapID == criteria.SearchNumber.Value) ||
+ (BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value);
}
}
if (match)
- match &= criteria.Collection?.Beatmaps.Contains(Beatmap) ?? true;
+ match &= criteria.Collection?.Beatmaps.Contains(BeatmapInfo) ?? true;
if (match && criteria.RulesetCriteria != null)
- match &= criteria.RulesetCriteria.Matches(Beatmap);
+ match &= criteria.RulesetCriteria.Matches(BeatmapInfo);
Filtered.Value = !match;
}
@@ -89,13 +89,13 @@ namespace osu.Game.Screens.Select.Carousel
{
default:
case SortMode.Difficulty:
- var ruleset = Beatmap.RulesetID.CompareTo(otherBeatmap.Beatmap.RulesetID);
+ var ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID);
if (ruleset != 0) return ruleset;
- return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty);
+ return BeatmapInfo.StarDifficulty.CompareTo(otherBeatmap.BeatmapInfo.StarDifficulty);
}
}
- public override string ToString() => Beatmap.ToString();
+ public override string ToString() => BeatmapInfo.ToString();
}
}
diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs
index 00c2c2cb4a..0d7882bf17 100644
--- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs
+++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs
@@ -47,8 +47,8 @@ namespace osu.Game.Screens.Select.Carousel
{
if (LastSelected == null || LastSelected.Filtered.Value)
{
- if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)) is BeatmapInfo recommended)
- return Children.OfType().First(b => b.Beatmap == recommended);
+ if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended)
+ return Children.OfType().First(b => b.BeatmapInfo == recommended);
}
return base.GetNextToSelect();
@@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select.Carousel
///
/// All beatmaps which are not filtered and valid for display.
///
- protected IEnumerable ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value || b.State.Value == CarouselItemState.Selected).Select(b => b.Beatmap);
+ protected IEnumerable ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value || b.State.Value == CarouselItemState.Selected).Select(b => b.BeatmapInfo);
private int compareUsingAggregateMax(CarouselBeatmapSet other, Func func)
{
diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
index 633ef9297e..5940911d4a 100644
--- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
+++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs
@@ -40,7 +40,7 @@ namespace osu.Game.Screens.Select.Carousel
private const float height = MAX_HEIGHT * 0.6f;
- private readonly BeatmapInfo beatmap;
+ private readonly BeatmapInfo beatmapInfo;
private Sprite background;
@@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select.Carousel
public DrawableCarouselBeatmap(CarouselBeatmap panel)
{
- beatmap = panel.Beatmap;
+ beatmapInfo = panel.BeatmapInfo;
Item = panel;
}
@@ -109,7 +109,7 @@ namespace osu.Game.Screens.Select.Carousel
Origin = Anchor.CentreLeft,
Children = new Drawable[]
{
- new DifficultyIcon(beatmap, shouldShowTooltip: false)
+ new DifficultyIcon(beatmapInfo, shouldShowTooltip: false)
{
Scale = new Vector2(1.8f),
},
@@ -129,7 +129,7 @@ namespace osu.Game.Screens.Select.Carousel
{
new OsuSpriteText
{
- Text = beatmap.Version,
+ Text = beatmapInfo.Version,
Font = OsuFont.GetFont(size: 20),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
@@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Carousel
},
new OsuSpriteText
{
- Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author.Username}",
+ Text = $"{(beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata).Author.Username}",
Font = OsuFont.GetFont(italics: true),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
@@ -156,7 +156,7 @@ namespace osu.Game.Screens.Select.Carousel
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
- new TopLocalRank(beatmap)
+ new TopLocalRank(beatmapInfo)
{
Scale = new Vector2(0.8f),
Size = new Vector2(40, 20)
@@ -200,7 +200,7 @@ namespace osu.Game.Screens.Select.Carousel
protected override bool OnClick(ClickEvent e)
{
if (Item.State.Value == CarouselItemState.Selected)
- startRequested?.Invoke(beatmap);
+ startRequested?.Invoke(beatmapInfo);
return base.OnClick(e);
}
@@ -216,7 +216,7 @@ namespace osu.Game.Screens.Select.Carousel
if (Item.State.Value != CarouselItemState.Collapsed)
{
// We've potentially cancelled the computation above so a new bindable is required.
- starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token);
+ starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmapInfo, (starDifficultyCancellationSource = new CancellationTokenSource()).Token);
starDifficultyBindable.BindValueChanged(d =>
{
starCounter.Current = (float)(d.NewValue?.Stars ?? 0);
@@ -233,13 +233,13 @@ namespace osu.Game.Screens.Select.Carousel
List
protected abstract BeatmapDetailArea CreateBeatmapDetailArea();
- public void Edit(BeatmapInfo beatmap = null)
+ public void Edit(BeatmapInfo beatmapInfo = null)
{
if (!AllowEditing)
throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled");
- Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce);
+ Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce);
this.Push(new EditorLoader());
}
///
/// Call to make a selection and perform the default action for this SongSelect.
///
- /// An optional beatmap to override the current carousel selection.
+ /// An optional beatmap to override the current carousel selection.
/// An optional ruleset to override the current carousel selection.
/// An optional custom action to perform instead of .
- public void FinaliseSelection(BeatmapInfo beatmap = null, RulesetInfo ruleset = null, Action customStartAction = null)
+ public void FinaliseSelection(BeatmapInfo beatmapInfo = null, RulesetInfo ruleset = null, Action customStartAction = null)
{
// This is very important as we have not yet bound to screen-level bindables before the carousel load is completed.
if (!Carousel.BeatmapSetsLoaded)
@@ -377,10 +377,10 @@ namespace osu.Game.Screens.Select
// avoid attempting to continue before a selection has been obtained.
// this could happen via a user interaction while the carousel is still in a loading state.
- if (Carousel.SelectedBeatmap == null) return;
+ if (Carousel.SelectedBeatmapInfo == null) return;
- if (beatmap != null)
- Carousel.SelectBeatmap(beatmap);
+ if (beatmapInfo != null)
+ Carousel.SelectBeatmap(beatmapInfo);
if (selectionChangedDebounce?.Completed == false)
{
@@ -435,18 +435,18 @@ namespace osu.Game.Screens.Select
}
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
- private BeatmapInfo beatmapNoDebounce;
+ private BeatmapInfo beatmapInfoNoDebounce;
private RulesetInfo rulesetNoDebounce;
- private void updateSelectedBeatmap(BeatmapInfo beatmap)
+ private void updateSelectedBeatmap(BeatmapInfo beatmapInfo)
{
- if (beatmap == null && beatmapNoDebounce == null)
+ if (beatmapInfo == null && beatmapInfoNoDebounce == null)
return;
- if (beatmap?.Equals(beatmapNoDebounce) == true)
+ if (beatmapInfo?.Equals(beatmapInfoNoDebounce) == true)
return;
- beatmapNoDebounce = beatmap;
+ beatmapInfoNoDebounce = beatmapInfo;
performUpdateSelected();
}
@@ -467,12 +467,12 @@ namespace osu.Game.Screens.Select
///
private void performUpdateSelected()
{
- var beatmap = beatmapNoDebounce;
+ var beatmap = beatmapInfoNoDebounce;
var ruleset = rulesetNoDebounce;
selectionChangedDebounce?.Cancel();
- if (beatmapNoDebounce == null)
+ if (beatmapInfoNoDebounce == null)
run();
else
selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
@@ -803,11 +803,11 @@ namespace osu.Game.Screens.Select
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap));
}
- private void clearScores(BeatmapInfo beatmap)
+ private void clearScores(BeatmapInfo beatmapInfo)
{
- if (beatmap == null || beatmap.ID <= 0) return;
+ if (beatmapInfo == null || beatmapInfo.ID <= 0) return;
- dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap, () =>
+ dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmapInfo, () =>
// schedule done here rather than inside the dialog as the dialog may fade out and never callback.
Schedule(() => BeatmapDetails.Refresh())));
}
diff --git a/osu.Game/Screens/Spectate/GameplayState.cs b/osu.Game/Screens/Spectate/SpectatorGameplayState.cs
similarity index 81%
rename from osu.Game/Screens/Spectate/GameplayState.cs
rename to osu.Game/Screens/Spectate/SpectatorGameplayState.cs
index 4579b9c07c..6ca1ac9a0a 100644
--- a/osu.Game/Screens/Spectate/GameplayState.cs
+++ b/osu.Game/Screens/Spectate/SpectatorGameplayState.cs
@@ -8,9 +8,9 @@ using osu.Game.Scoring;
namespace osu.Game.Screens.Spectate
{
///
- /// The gameplay state of a spectated user. This class is immutable.
+ /// An immutable spectator gameplay state.
///
- public class GameplayState
+ public class SpectatorGameplayState
{
///
/// The score which the user is playing.
@@ -27,7 +27,7 @@ namespace osu.Game.Screens.Spectate
///
public readonly WorkingBeatmap Beatmap;
- public GameplayState(Score score, Ruleset ruleset, WorkingBeatmap beatmap)
+ public SpectatorGameplayState(Score score, Ruleset ruleset, WorkingBeatmap beatmap)
{
Score = score;
Ruleset = ruleset;
diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs
index f0a68ea078..7861d4cb72 100644
--- a/osu.Game/Screens/Spectate/SpectatorScreen.cs
+++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs
@@ -43,7 +43,7 @@ namespace osu.Game.Screens.Spectate
private readonly IBindableDictionary playingUserStates = new BindableDictionary();
private readonly Dictionary userMap = new Dictionary();
- private readonly Dictionary gameplayStates = new Dictionary();
+ private readonly Dictionary gameplayStates = new Dictionary();
private IBindable> managerUpdated;
@@ -165,7 +165,7 @@ namespace osu.Game.Screens.Spectate
{
ScoreInfo = new ScoreInfo
{
- Beatmap = resolvedBeatmap,
+ BeatmapInfo = resolvedBeatmap,
User = user,
Mods = spectatorState.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(),
Ruleset = resolvedRuleset.RulesetInfo,
@@ -173,7 +173,7 @@ namespace osu.Game.Screens.Spectate
Replay = new Replay { HasReceivedAllFrames = false },
};
- var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap));
+ var gameplayState = new SpectatorGameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap));
gameplayStates[userId] = gameplayState;
Schedule(() => StartGameplay(userId, gameplayState));
@@ -190,8 +190,8 @@ namespace osu.Game.Screens.Spectate
/// Starts gameplay for a user.
///
/// The user to start gameplay for.
- /// The gameplay state.
- protected abstract void StartGameplay(int userId, [NotNull] GameplayState gameplayState);
+ /// The gameplay state.
+ protected abstract void StartGameplay(int userId, [NotNull] SpectatorGameplayState spectatorGameplayState);
///
/// Ends gameplay for a user.
diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs
index d3adae5c8c..8e03bddb4d 100644
--- a/osu.Game/Skinning/DefaultSkin.cs
+++ b/osu.Game/Skinning/DefaultSkin.cs
@@ -68,6 +68,7 @@ namespace osu.Game.Skinning
var score = container.OfType().FirstOrDefault();
var accuracy = container.OfType().FirstOrDefault();
var combo = container.OfType().FirstOrDefault();
+ var ppCounter = container.OfType().FirstOrDefault();
if (score != null)
{
@@ -81,6 +82,13 @@ namespace osu.Game.Skinning
score.Position = new Vector2(0, vertical_offset);
+ if (ppCounter != null)
+ {
+ ppCounter.Y = score.Position.Y + ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).Y - 4;
+ ppCounter.Origin = Anchor.TopCentre;
+ ppCounter.Anchor = Anchor.TopCentre;
+ }
+
if (accuracy != null)
{
accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5);
@@ -123,6 +131,7 @@ namespace osu.Game.Skinning
new SongProgress(),
new BarHitErrorMeter(),
new BarHitErrorMeter(),
+ new PerformancePointsCounter()
}
};
diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs
index e6ddeba316..2093182dcc 100644
--- a/osu.Game/Skinning/LegacyBeatmapSkin.cs
+++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs
@@ -20,8 +20,8 @@ namespace osu.Game.Skinning
protected override bool AllowManiaSkin => false;
protected override bool UseCustomSampleBanks => true;
- public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, IStorageResourceProvider resources)
- : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path)
+ public LegacyBeatmapSkin(BeatmapInfo beatmapInfo, IResourceStore storage, IStorageResourceProvider resources)
+ : base(createSkinInfo(beatmapInfo), new LegacySkinResourceStore(beatmapInfo.BeatmapSet, storage), resources, beatmapInfo.Path)
{
// Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
Configuration.AllowDefaultComboColoursFallback = false;
@@ -76,7 +76,7 @@ namespace osu.Game.Skinning
return base.GetSample(sampleInfo);
}
- private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
- new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString };
+ private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) =>
+ new SkinInfo { Name = beatmapInfo.ToString(), Creator = beatmapInfo.Metadata?.AuthorString };
}
}
diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs
index b6cb8fc7a4..92441f40da 100644
--- a/osu.Game/Skinning/Skin.cs
+++ b/osu.Game/Skinning/Skin.cs
@@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.IO;
using osu.Game.Screens.Play.HUD;
@@ -55,13 +56,20 @@ namespace osu.Game.Skinning
if (bytes == null)
continue;
- string jsonContent = Encoding.UTF8.GetString(bytes);
- var deserializedContent = JsonConvert.DeserializeObject>(jsonContent);
+ try
+ {
+ string jsonContent = Encoding.UTF8.GetString(bytes);
+ var deserializedContent = JsonConvert.DeserializeObject>(jsonContent);
- if (deserializedContent == null)
- continue;
+ if (deserializedContent == null)
+ continue;
- DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray();
+ DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray();
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to load skin configuration.");
+ }
}
}
diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs
index edeb17cbad..3842acab74 100644
--- a/osu.Game/Skinning/SkinManager.cs
+++ b/osu.Game/Skinning/SkinManager.cs
@@ -207,7 +207,7 @@ namespace osu.Game.Skinning
Name = skin.SkinInfo.Name + " (modified)",
Creator = skin.SkinInfo.Creator,
InstantiationInfo = skin.SkinInfo.InstantiationInfo,
- }).Result;
+ }).Result.Value;
}
public void Save(Skin skin)
diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs
index c22ca0d8c0..66db965803 100644
--- a/osu.Game/Storyboards/CommandLoop.cs
+++ b/osu.Game/Storyboards/CommandLoop.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
namespace osu.Game.Storyboards
@@ -8,20 +9,31 @@ namespace osu.Game.Storyboards
public class CommandLoop : CommandTimelineGroup
{
public double LoopStartTime;
- public int LoopCount;
+
+ ///
+ /// The total number of times this loop is played back. Always greater than zero.
+ ///
+ public readonly int TotalIterations;
public override double StartTime => LoopStartTime + CommandsStartTime;
- public override double EndTime => StartTime + CommandsDuration * LoopCount;
+ public override double EndTime => StartTime + CommandsDuration * TotalIterations;
- public CommandLoop(double startTime, int loopCount)
+ ///
+ /// Construct a new command loop.
+ ///
+ /// The start time of the loop.
+ /// The number of times the loop should repeat. Should be greater than zero. Zero means a single playback.
+ public CommandLoop(double startTime, int repeatCount)
{
+ if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount));
+
LoopStartTime = startTime;
- LoopCount = loopCount;
+ TotalIterations = repeatCount + 1;
}
public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0)
{
- for (var loop = 0; loop < LoopCount; loop++)
+ for (var loop = 0; loop < TotalIterations; loop++)
{
var loopOffset = LoopStartTime + loop * CommandsDuration;
foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset))
@@ -30,6 +42,6 @@ namespace osu.Game.Storyboards
}
public override string ToString()
- => $"{LoopStartTime} x{LoopCount}";
+ => $"{LoopStartTime} x{TotalIterations}";
}
}
diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs
index bf87e7d10e..6fb2f5994b 100644
--- a/osu.Game/Storyboards/StoryboardSprite.cs
+++ b/osu.Game/Storyboards/StoryboardSprite.cs
@@ -1,13 +1,13 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osuTK;
-using osu.Framework.Graphics;
-using osu.Game.Storyboards.Drawables;
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
+using osu.Framework.Graphics;
+using osu.Game.Storyboards.Drawables;
+using osuTK;
namespace osu.Game.Storyboards
{
@@ -78,9 +78,9 @@ namespace osu.Game.Storyboards
InitialPosition = initialPosition;
}
- public CommandLoop AddLoop(double startTime, int loopCount)
+ public CommandLoop AddLoop(double startTime, int repeatCount)
{
- var loop = new CommandLoop(startTime, loopCount);
+ var loop = new CommandLoop(startTime, repeatCount);
loops.Add(loop);
return loop;
}
diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs
index 27162b1d66..5c522058d9 100644
--- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs
+++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs
@@ -111,8 +111,8 @@ namespace osu.Game.Tests.Beatmaps
public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.DarkGoldenrod;
- public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours)
- : base(beatmap, new ResourceStore(), null)
+ public TestBeatmapSkin(BeatmapInfo beatmapInfo, bool hasColours)
+ : base(beatmapInfo, new ResourceStore(), null)
{
if (hasColours)
{
diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs
index 5ce6aae647..719d31b092 100644
--- a/osu.Game/Tests/TestScoreInfo.cs
+++ b/osu.Game/Tests/TestScoreInfo.cs
@@ -23,7 +23,7 @@ namespace osu.Game.Tests
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
};
- Beatmap = new TestBeatmap(ruleset).BeatmapInfo;
+ BeatmapInfo = new TestBeatmap(ruleset).BeatmapInfo;
Ruleset = ruleset;
RulesetID = ruleset.ID ?? 0;
diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs
index 881c4bab02..77db697cb6 100644
--- a/osu.Game/Tests/Visual/OsuGameTestScene.cs
+++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs
@@ -22,6 +22,7 @@ using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osuTK.Graphics;
+using IntroSequence = osu.Game.Configuration.IntroSequence;
namespace osu.Game.Tests.Visual
{
@@ -83,8 +84,17 @@ namespace osu.Game.Tests.Visual
protected void PushAndConfirm(Func newScreen)
{
Screen screen = null;
- AddStep("Push new screen", () => Game.ScreenStack.Push(screen = newScreen()));
- AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
+ IScreen previousScreen = null;
+
+ AddStep("Push new screen", () =>
+ {
+ previousScreen = Game.ScreenStack.CurrentScreen;
+ Game.ScreenStack.Push(screen = newScreen());
+ });
+
+ AddUntilStep("Wait for new screen", () => screen.IsLoaded
+ && Game.ScreenStack.CurrentScreen != previousScreen
+ && previousScreen.GetChildScreen() == screen);
}
protected void ConfirmAtMainMenu() => AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
@@ -117,7 +127,8 @@ namespace osu.Game.Tests.Visual
public new Bindable> SelectedMods => base.SelectedMods;
- // if we don't do this, when running under nUnit the version that gets populated is that of nUnit.
+ // if we don't apply these changes, when running under nUnit the version that gets populated is that of nUnit.
+ public override Version AssemblyVersion => new Version(0, 0);
public override string Version => "test game";
protected override Loader CreateLoader() => new TestLoader();
@@ -133,6 +144,9 @@ namespace osu.Game.Tests.Visual
protected override void LoadComplete()
{
base.LoadComplete();
+
+ LocalConfig.SetValue(OsuSetting.IntroSequence, IntroSequence.Circles);
+
API.Login("Rhythm Champion", "osu!");
Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true);
diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs
index 42cf826bd4..b2f5b1754f 100644
--- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs
+++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
+using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
@@ -24,24 +25,25 @@ namespace osu.Game.Tests.Visual
base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock())));
}
- [BackgroundDependencyLoader]
- private void load()
- {
- Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2;
- }
-
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(new EditorClock());
- var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+ var playable = GetPlayableBeatmap();
dependencies.CacheAs(new EditorBeatmap(playable));
return dependencies;
}
+ protected virtual IBeatmap GetPlayableBeatmap()
+ {
+ var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset);
+ playable.Difficulty.CircleSize = 2;
+ return playable;
+ }
+
protected override void LoadComplete()
{
base.LoadComplete();
diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs
index b30be05ac4..aa46b516bf 100644
--- a/osu.Game/Tests/Visual/ScreenTestScene.cs
+++ b/osu.Game/Tests/Visual/ScreenTestScene.cs
@@ -4,6 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Logging;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Screens;
@@ -32,6 +33,9 @@ namespace osu.Game.Tests.Visual
content = new Container { RelativeSizeAxes = Axes.Both },
DialogOverlay = new DialogOverlay()
});
+
+ Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");
+ Stack.ScreenExited += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}");
}
protected void LoadScreen(OsuScreen screen) => Stack.Push(screen);
diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs
index 5e5f20b307..d68984b144 100644
--- a/osu.Game/Tests/Visual/TestPlayer.cs
+++ b/osu.Game/Tests/Visual/TestPlayer.cs
@@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual
if (autoplayMod != null)
{
- DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value));
+ DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayState.Beatmap, Mods.Value));
return;
}
diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs
index 75aa4866ff..91bcb37fcc 100644
--- a/osu.Game/Users/UserActivity.cs
+++ b/osu.Game/Users/UserActivity.cs
@@ -27,13 +27,13 @@ namespace osu.Game.Users
public abstract class InGame : UserActivity
{
- public BeatmapInfo Beatmap { get; }
+ public BeatmapInfo BeatmapInfo { get; }
public RulesetInfo Ruleset { get; }
- protected InGame(BeatmapInfo info, RulesetInfo ruleset)
+ protected InGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
{
- Beatmap = info;
+ BeatmapInfo = beatmapInfo;
Ruleset = ruleset;
}
@@ -42,8 +42,8 @@ namespace osu.Game.Users
public class InMultiplayerGame : InGame
{
- public InMultiplayerGame(BeatmapInfo beatmap, RulesetInfo ruleset)
- : base(beatmap, ruleset)
+ public InMultiplayerGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
+ : base(beatmapInfo, ruleset)
{
}
@@ -52,27 +52,27 @@ namespace osu.Game.Users
public class InPlaylistGame : InGame
{
- public InPlaylistGame(BeatmapInfo beatmap, RulesetInfo ruleset)
- : base(beatmap, ruleset)
+ public InPlaylistGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
+ : base(beatmapInfo, ruleset)
{
}
}
public class InSoloGame : InGame
{
- public InSoloGame(BeatmapInfo info, RulesetInfo ruleset)
- : base(info, ruleset)
+ public InSoloGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset)
+ : base(beatmapInfo, ruleset)
{
}
}
public class Editing : UserActivity
{
- public BeatmapInfo Beatmap { get; }
+ public BeatmapInfo BeatmapInfo { get; }
public Editing(BeatmapInfo info)
{
- Beatmap = info;
+ BeatmapInfo = info;
}
public override string Status => @"Editing a beatmap";
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index ba118c5240..4877ddf725 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -20,12 +20,12 @@
-
+
-
-
-
-
+
+
+
+
@@ -35,10 +35,10 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 37931d0c38..edce9d27fe 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,8 +70,8 @@
-
-
+
+
@@ -93,12 +93,12 @@
-
+
-
+