diff --git a/Directory.Build.props b/Directory.Build.props
index 2d3478f256..551cb75077 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,9 +16,9 @@
-
+
-
+
$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
diff --git a/README.md b/README.md
index 7c749f3422..86c42dae12 100644
--- a/README.md
+++ b/README.md
@@ -34,6 +34,8 @@ If you are looking to install or test osu! without setting up a development envi
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
+- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
+
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
diff --git a/global.json b/global.json
index a9a531f59c..10b61047ac 100644
--- a/global.json
+++ b/global.json
@@ -5,6 +5,6 @@
"version": "3.1.100"
},
"msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.1.1"
+ "Microsoft.Build.Traversal": "2.2.3"
}
}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 2d531cf01e..97812402a3 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
index ab840e1c46..e8c2472c3b 100644
--- a/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
+++ b/osu.Game.Rulesets.Mania.Tests/TestSceneOutOfOrderHits.cs
@@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Mania.Tests
objects.Add(new Note { StartTime = time });
+ // don't hit the first note
if (i > 0)
{
frames.Add(new ManiaReplayFrame(time + 10, ManiaAction.Key1));
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json
index d49ffa01c5..6f1d45ad8c 100644
--- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json
@@ -6,20 +6,20 @@
"EndTime": 2750.0,
"Column": 1,
"NodeSamples": [
- ["normal-hitnormal"],
- ["soft-hitnormal"],
- ["drum-hitnormal"]
+ ["Gameplay/normal-hitnormal"],
+ ["Gameplay/soft-hitnormal"],
+ ["Gameplay/drum-hitnormal"]
],
- "Samples": ["-hitnormal"]
+ "Samples": ["Gameplay/-hitnormal"]
}, {
"StartTime": 1875.0,
"EndTime": 2750.0,
"Column": 0,
"NodeSamples": [
- ["soft-hitnormal"],
- ["drum-hitnormal"]
+ ["Gameplay/soft-hitnormal"],
+ ["Gameplay/drum-hitnormal"]
],
- "Samples": ["-hitnormal"]
+ "Samples": ["Gameplay/-hitnormal"]
}]
}, {
"StartTime": 3750.0,
@@ -27,7 +27,7 @@
"StartTime": 3750.0,
"EndTime": 3750.0,
"Column": 3,
- "Samples": ["normal-hitnormal"]
+ "Samples": ["Gameplay/normal-hitnormal"]
}]
}]
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json
index 1aca75a796..fd0c0cad60 100644
--- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json
@@ -6,10 +6,10 @@
"EndTime": 1500.0,
"Column": 0,
"NodeSamples": [
- ["normal-hitnormal"],
+ ["Gameplay/normal-hitnormal"],
[]
],
- "Samples": ["normal-hitnormal"]
+ "Samples": ["Gameplay/normal-hitnormal"]
}]
}, {
"StartTime": 2000.0,
@@ -18,10 +18,10 @@
"EndTime": 3000.0,
"Column": 2,
"NodeSamples": [
- ["drum-hitnormal"],
+ ["Gameplay/drum-hitnormal"],
[]
],
- "Samples": ["drum-hitnormal"]
+ "Samples": ["Gameplay/drum-hitnormal"]
}]
}]
}
\ No newline at end of file
diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json
index e3768a90d7..e07bd3c47c 100644
--- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json
+++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json
@@ -5,17 +5,17 @@
"StartTime": 8470.0,
"EndTime": 8470.0,
"Column": 0,
- "Samples": ["normal-hitnormal", "normal-hitclap"]
+ "Samples": ["Gameplay/normal-hitnormal", "Gameplay/normal-hitclap"]
}, {
"StartTime": 8626.470587768974,
"EndTime": 8626.470587768974,
"Column": 1,
- "Samples": ["normal-hitnormal"]
+ "Samples": ["Gameplay/normal-hitnormal"]
}, {
"StartTime": 8782.941175537948,
"EndTime": 8782.941175537948,
"Column": 2,
- "Samples": ["normal-hitnormal", "normal-hitclap"]
+ "Samples": ["Gameplay/normal-hitnormal", "Gameplay/normal-hitclap"]
}]
}]
}
diff --git a/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs b/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs
new file mode 100644
index 0000000000..23d9d265be
--- /dev/null
+++ b/osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs
@@ -0,0 +1,41 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Rulesets.UI;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Tests
+{
+ public class TestPlayfieldBorder : OsuTestScene
+ {
+ public TestPlayfieldBorder()
+ {
+ Bindable playfieldBorderStyle = new Bindable();
+
+ AddStep("add drawables", () =>
+ {
+ Child = new Container
+ {
+ Size = new Vector2(400, 300),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ new PlayfieldBorder
+ {
+ PlayfieldBorderStyle = { BindTarget = playfieldBorderStyle }
+ }
+ }
+ };
+ });
+
+ AddStep("Set none", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.None);
+ AddStep("Set corners", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Corners);
+ AddStep("Set full", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Full);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
index f76635a932..e8272057f3 100644
--- a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
+++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs
@@ -3,6 +3,7 @@
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Configuration
{
@@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
Set(OsuRulesetSetting.SnakingInSliders, true);
Set(OsuRulesetSetting.SnakingOutSliders, true);
Set(OsuRulesetSetting.ShowCursorTrail, true);
+ Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
}
}
@@ -26,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
{
SnakingInSliders,
SnakingOutSliders,
- ShowCursorTrail
+ ShowCursorTrail,
+ PlayfieldBorderStyle,
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
index 912a705d16..edd684d886 100644
--- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
+++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
@@ -56,7 +56,18 @@ namespace osu.Game.Rulesets.Osu.Edit
[BackgroundDependencyLoader]
private void load()
{
- LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
+ LayerBelowRuleset.AddRange(new Drawable[]
+ {
+ new PlayfieldBorder
+ {
+ RelativeSizeAxes = Axes.Both,
+ PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
+ },
+ distanceSnapGridContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ });
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
index 2263e2b2f4..8c819c4773 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs
@@ -11,7 +11,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.UI;
-using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private OsuInputManager inputManager;
- private GameplayClock gameplayClock;
+ private IFrameStableClock gameplayClock;
private List replayFrames;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 937473e824..6841ecd23c 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -64,8 +64,8 @@ namespace osu.Game.Rulesets.Osu.Mods
///
private const float target_clamp = 1;
- private readonly float targetBreakMultiplier = 0;
- private readonly float easing = 1;
+ private readonly float targetBreakMultiplier;
+ private readonly float easing;
private readonly CompositeDrawable restrictTo;
@@ -86,6 +86,9 @@ namespace osu.Game.Rulesets.Osu.Mods
{
this.restrictTo = restrictTo;
this.beatmap = beatmap;
+
+ targetBreakMultiplier = 0;
+ easing = 1;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index d7582f3196..bb2213aa31 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -6,6 +6,7 @@ using System.Linq;
using osu.Framework.Bindables;
using System.Collections.Generic;
using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
@@ -38,20 +39,25 @@ namespace osu.Game.Rulesets.Osu.Mods
protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state)
{
- if (!(drawable is DrawableOsuHitObject drawableOsu))
+ if (!(drawable is DrawableOsuHitObject))
return;
- var h = drawableOsu.HitObject;
-
//todo: expose and hide spinner background somehow
switch (drawable)
{
case DrawableHitCircle circle:
// we only want to see the approach circle
- using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
- circle.CirclePiece.Hide();
+ applyCirclePieceState(circle, circle.CirclePiece);
+ break;
+ case DrawableSliderTail sliderTail:
+ applyCirclePieceState(sliderTail);
+ break;
+
+ case DrawableSliderRepeat sliderRepeat:
+ // show only the repeat arrow
+ applyCirclePieceState(sliderRepeat, sliderRepeat.CirclePiece);
break;
case DrawableSlider slider:
@@ -61,6 +67,13 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
+ private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null)
+ {
+ var h = hitObject.HitObject;
+ using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
+ (hitCircle ?? hitObject).Hide();
+ }
+
private void applySliderState(DrawableSlider slider)
{
((PlaySliderBody)slider.Body.Drawable).AccentColour = slider.AccentColour.Value.Opacity(0);
diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
index 4ef9bbe091..50727d590a 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs
@@ -17,12 +17,16 @@ using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Game.Rulesets.Osu.Configuration;
using osuTK;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuPlayfield : Playfield
{
+ private readonly PlayfieldBorder playfieldBorder;
private readonly ProxyContainer approachCircles;
private readonly ProxyContainer spinnerProxies;
private readonly JudgementContainer judgementLayer;
@@ -33,12 +37,19 @@ namespace osu.Game.Rulesets.Osu.UI
protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer();
+ private readonly Bindable playfieldBorderStyle = new BindableBool();
+
private readonly IDictionary> poolDictionary = new Dictionary>();
public OsuPlayfield()
{
InternalChildren = new Drawable[]
{
+ playfieldBorder = new PlayfieldBorder
+ {
+ RelativeSizeAxes = Axes.Both,
+ Depth = 3
+ },
spinnerProxies = new ProxyContainer
{
RelativeSizeAxes = Axes.Both
@@ -76,6 +87,12 @@ namespace osu.Game.Rulesets.Osu.UI
AddRangeInternal(poolDictionary.Values);
}
+ [BackgroundDependencyLoader(true)]
+ private void load(OsuRulesetConfigManager config)
+ {
+ config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
+ }
+
public override void Add(DrawableHitObject h)
{
h.OnNewResult += onNewResult;
diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
index 3870f303b4..705ba3e929 100644
--- a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
+++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs
@@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets.Osu.Configuration;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.UI
{
@@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Osu.UI
LabelText = "Cursor trail",
Current = config.GetBindable(OsuRulesetSetting.ShowCursorTrail)
},
+ new SettingsEnumDropdown
+ {
+ LabelText = "Playfield border style",
+ Current = config.GetBindable(OsuRulesetSetting.PlayfieldBorderStyle),
+ },
};
}
}
diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
index a804ea5f82..c88480d18f 100644
--- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs
@@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
get
{
foreach (var name in source.LookupNames)
- yield return $"taiko-{name}";
+ yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-");
foreach (var name in source.LookupNames)
yield return name;
diff --git a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs
index fde42bec04..9bfb6aa839 100644
--- a/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs
+++ b/osu.Game.Rulesets.Taiko/UI/DrumRollHitContainer.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Performance;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
index b6e1af57fd..4b9e9dd88c 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs
@@ -410,13 +410,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var hitObjects = decoder.Decode(stream).HitObjects;
- Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
- Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
- Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
- Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
// The control point at the end time of the slider should be applied
- Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
+ Assert.AreEqual("Gameplay/soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First());
}
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
@@ -432,9 +432,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var hitObjects = decoder.Decode(stream).HitObjects;
- Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
- Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
- Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
}
static HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]);
@@ -452,7 +452,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[0]).LookupNames.First());
Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First());
- Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
+ Assert.AreEqual("Gameplay/normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First());
Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First());
Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume);
}
diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
index 80fbda8e1d..b941313103 100644
--- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
+++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs
@@ -821,15 +821,13 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get();
- await manager.Import(temp);
-
- var imported = manager.GetAllUsableBeatmapSets();
+ var importedSet = await manager.Import(temp);
ensureLoaded(osu);
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
- return imported.LastOrDefault();
+ return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID);
}
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
index 58cc324233..de46f9d1cf 100644
--- a/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectAccentColour.cs
@@ -81,8 +81,8 @@ namespace osu.Game.Tests.Gameplay
private class TestHitObjectWithCombo : ConvertHitObject, IHasComboInformation
{
- public bool NewCombo { get; set; } = false;
- public int ComboOffset { get; } = 0;
+ public bool NewCombo { get; set; }
+ public int ComboOffset => 0;
public Bindable IndexInCurrentComboBindable { get; } = new Bindable();
diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
new file mode 100644
index 0000000000..fba0d92d4b
--- /dev/null
+++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs
@@ -0,0 +1,196 @@
+// 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;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Configuration;
+using osu.Game.Graphics.Backgrounds;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Tests.Visual.Background
+{
+ public class TestSceneSeasonalBackgroundLoader : ScreenTestScene
+ {
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ [Resolved]
+ private SessionStatics statics { get; set; }
+
+ [Cached(typeof(LargeTextureStore))]
+ private LookupLoggingTextureStore textureStore = new LookupLoggingTextureStore();
+
+ private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
+
+ private SeasonalBackgroundLoader backgroundLoader;
+ private Container backgroundContainer;
+
+ // in real usages these would be online URLs, but correct execution of this test
+ // shouldn't be coupled to existence of online assets.
+ private static readonly List seasonal_background_urls = new List
+ {
+ "Backgrounds/bg2",
+ "Backgrounds/bg4",
+ "Backgrounds/bg3"
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(LargeTextureStore wrappedStore)
+ {
+ textureStore.AddStore(wrappedStore);
+
+ Add(backgroundContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Both
+ });
+ }
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ // reset API response in statics to avoid test crosstalk.
+ statics.Set(Static.SeasonalBackgrounds, null);
+ textureStore.PerformedLookups.Clear();
+ dummyAPI.SetState(APIState.Online);
+
+ backgroundContainer.Clear();
+ });
+
+ [TestCase(-5)]
+ [TestCase(5)]
+ public void TestAlwaysSeasonal(int daysOffset)
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
+
+ createLoader();
+
+ for (int i = 0; i < 4; ++i)
+ loadNextBackground();
+
+ AddAssert("all backgrounds cycled", () => new HashSet(textureStore.PerformedLookups).SetEquals(seasonal_background_urls));
+ }
+
+ [TestCase(-5)]
+ [TestCase(5)]
+ public void TestNeverSeasonal(int daysOffset)
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(daysOffset));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Never);
+
+ createLoader();
+
+ assertNoBackgrounds();
+ }
+
+ [Test]
+ public void TestSometimesInSeason()
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(5));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
+
+ createLoader();
+
+ assertAnyBackground();
+ }
+
+ [Test]
+ public void TestSometimesOutOfSeason()
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(-10));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Sometimes);
+
+ createLoader();
+
+ assertNoBackgrounds();
+ }
+
+ [Test]
+ public void TestDelayedConnectivity()
+ {
+ registerBackgroundsResponse(DateTimeOffset.Now.AddDays(30));
+ setSeasonalBackgroundMode(SeasonalBackgroundMode.Always);
+ AddStep("go offline", () => dummyAPI.SetState(APIState.Offline));
+
+ createLoader();
+ assertNoBackgrounds();
+
+ AddStep("go online", () => dummyAPI.SetState(APIState.Online));
+
+ assertAnyBackground();
+ }
+
+ private void registerBackgroundsResponse(DateTimeOffset endDate)
+ => AddStep("setup request handler", () =>
+ {
+ dummyAPI.HandleRequest = request =>
+ {
+ if (dummyAPI.State.Value != APIState.Online || !(request is GetSeasonalBackgroundsRequest backgroundsRequest))
+ return;
+
+ backgroundsRequest.TriggerSuccess(new APISeasonalBackgrounds
+ {
+ Backgrounds = seasonal_background_urls.Select(url => new APISeasonalBackground { Url = url }).ToList(),
+ EndDate = endDate
+ });
+ };
+ });
+
+ private void setSeasonalBackgroundMode(SeasonalBackgroundMode mode)
+ => AddStep($"set seasonal mode to {mode}", () => config.Set(OsuSetting.SeasonalBackgroundMode, mode));
+
+ private void createLoader()
+ => AddStep("create loader", () =>
+ {
+ if (backgroundLoader != null)
+ Remove(backgroundLoader);
+
+ Add(backgroundLoader = new SeasonalBackgroundLoader());
+ });
+
+ private void loadNextBackground()
+ {
+ SeasonalBackground background = null;
+
+ AddStep("create next background", () =>
+ {
+ background = backgroundLoader.LoadNextBackground();
+ LoadComponentAsync(background, bg => backgroundContainer.Child = bg);
+ });
+
+ AddUntilStep("background loaded", () => background.IsLoaded);
+ }
+
+ private void assertAnyBackground()
+ {
+ loadNextBackground();
+ AddAssert("background looked up", () => textureStore.PerformedLookups.Any());
+ }
+
+ private void assertNoBackgrounds()
+ {
+ AddAssert("no background available", () => backgroundLoader.LoadNextBackground() == null);
+ AddAssert("no lookups performed", () => !textureStore.PerformedLookups.Any());
+ }
+
+ private class LookupLoggingTextureStore : LargeTextureStore
+ {
+ public List PerformedLookups { get; } = new List();
+
+ public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT)
+ {
+ PerformedLookups.Add(name);
+ return base.Get(name, wrapModeS, wrapModeT);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.cs
new file mode 100644
index 0000000000..9501026edc
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableStoryboardSprite.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.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Skinning;
+using osu.Game.Storyboards;
+using osu.Game.Storyboards.Drawables;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneDrawableStoryboardSprite : SkinnableTestScene
+ {
+ protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
+
+ [Cached]
+ private Storyboard storyboard { get; set; } = new Storyboard();
+
+ [Test]
+ public void TestSkinSpriteDisallowedByDefault()
+ {
+ const string lookup_name = "hitcircleoverlay";
+
+ AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false);
+
+ AddStep("create sprites", () => SetContents(
+ () => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
+
+ assertSpritesFromSkin(false);
+ }
+
+ [Test]
+ public void TestAllowLookupFromSkin()
+ {
+ const string lookup_name = "hitcircleoverlay";
+
+ AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
+
+ AddStep("create sprites", () => SetContents(
+ () => createSprite(lookup_name, Anchor.Centre, Vector2.Zero)));
+
+ assertSpritesFromSkin(true);
+ }
+
+ private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
+ => new DrawableStoryboardSprite(
+ new StoryboardSprite(lookupName, origin, initialPosition)
+ ).With(s =>
+ {
+ s.LifetimeStart = double.MinValue;
+ s.LifetimeEnd = double.MaxValue;
+ });
+
+ private void assertSpritesFromSkin(bool fromSkin) =>
+ AddAssert($"sprites are {(fromSkin ? "from skin" : "from storyboard")}",
+ () => this.ChildrenOfType()
+ .All(sprite => sprite.ChildrenOfType().Any() == fromSkin));
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs
index 6e505b16c2..b86cb69eb4 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplaySamplePlayback.cs
@@ -9,7 +9,6 @@ using osu.Framework.Testing;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects.Drawables;
-using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using osu.Game.Skinning;
@@ -22,11 +21,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{
DrawableSlider slider = null;
DrawableSample[] samples = null;
- ISamplePlaybackDisabler gameplayClock = null;
+ ISamplePlaybackDisabler sampleDisabler = null;
AddStep("get variables", () =>
{
- gameplayClock = Player.ChildrenOfType().First();
+ sampleDisabler = Player;
slider = Player.ChildrenOfType().OrderBy(s => s.HitObject.StartTime).First();
samples = slider.ChildrenOfType().ToArray();
});
@@ -43,16 +42,16 @@ namespace osu.Game.Tests.Visual.Gameplay
return true;
});
- AddAssert("sample playback disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
+ AddAssert("sample playback disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
// because we are in frame stable context, it's quite likely that not all samples are "played" at this point.
// the important thing is that at least one started, and that sample has since stopped.
AddAssert("all looping samples stopped immediately", () => allStopped(allLoopingSounds));
AddUntilStep("all samples stopped eventually", () => allStopped(allSounds));
- AddAssert("sample playback still disabled", () => gameplayClock.SamplePlaybackDisabled.Value);
+ AddAssert("sample playback still disabled", () => sampleDisabler.SamplePlaybackDisabled.Value);
- AddUntilStep("seek finished, sample playback enabled", () => !gameplayClock.SamplePlaybackDisabled.Value);
+ AddUntilStep("seek finished, sample playback enabled", () => !sampleDisabler.SamplePlaybackDisabled.Value);
AddUntilStep("any sample is playing", () => Player.ChildrenOfType().Any(s => s.IsPlaying));
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index 603b5d4956..f9914e0193 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -2,29 +2,23 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
-using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
-using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
- public class TestSceneHUDOverlay : SkinnableTestScene
+ public class TestSceneHUDOverlay : OsuManualInputManagerTestScene
{
private HUDOverlay hudOverlay;
- private IEnumerable hudOverlays => CreatedDrawables.OfType();
-
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
@@ -37,17 +31,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
- AddRepeatStep("increase combo", () =>
- {
- foreach (var hud in hudOverlays)
- hud.ComboCounter.Current.Value++;
- }, 10);
+ AddRepeatStep("increase combo", () => { hudOverlay.ComboCounter.Current.Value++; }, 10);
- AddStep("reset combo", () =>
- {
- foreach (var hud in hudOverlays)
- hud.ComboCounter.Current.Value = 0;
- });
+ AddStep("reset combo", () => { hudOverlay.ComboCounter.Current.Value = 0; });
}
[Test]
@@ -77,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
createNew();
- AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
@@ -86,20 +72,42 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
}
+ [Test]
+ public void TestMomentaryShowHUD()
+ {
+ createNew();
+
+ HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
+
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
+
+ AddStep("set hud to never show", () => config.Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
+
+ AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
+
+ AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft));
+ AddUntilStep("wait for visible", () => hideTarget.IsPresent);
+
+ AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
+ AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
+
+ AddStep("set original config value", () => config.Set(OsuSetting.HUDVisibilityMode, originalConfigValue));
+ }
+
[Test]
public void TestExternalHideDoesntAffectConfig()
{
- bool originalConfigValue = false;
+ HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringGameplay;
createNew();
- AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.ShowInterface));
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
- AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode));
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
- AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.ShowInterface));
+ AddAssert("config unchanged", () => originalConfigValue == config.Get(OsuSetting.HUDVisibilityMode));
}
[Test]
@@ -113,14 +121,14 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set keycounter visible false", () =>
{
config.Set(OsuSetting.KeyOverlay, false);
- hudOverlays.ForEach(h => h.KeyCounter.AlwaysVisible.Value = false);
+ hudOverlay.KeyCounter.AlwaysVisible.Value = false;
});
- AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
+ AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent);
- AddStep("set showhud true", () => hudOverlays.ForEach(h => h.ShowHud.Value = true));
+ AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
@@ -131,22 +139,17 @@ namespace osu.Game.Tests.Visual.Gameplay
{
AddStep("create overlay", () =>
{
- SetContents(() =>
- {
- hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
+ hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
- // Add any key just to display the key counter visually.
- hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
+ // Add any key just to display the key counter visually.
+ hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
- hudOverlay.ComboCounter.Current.Value = 1;
+ hudOverlay.ComboCounter.Current.Value = 1;
- action?.Invoke(hudOverlay);
+ action?.Invoke(hudOverlay);
- return hudOverlay;
- });
+ Child = hudOverlay;
});
}
-
- protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
index 4fac7bb45f..9b31dd045a 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs
@@ -13,6 +13,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
using osu.Framework.Screens;
+using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
@@ -35,6 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestPlayerLoaderContainer container;
private TestPlayer player;
+ private bool epilepsyWarning;
+
[Resolved]
private AudioManager audioManager { get; set; }
@@ -59,6 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
+ Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
foreach (var mod in SelectedMods.Value.OfType())
mod.ApplyToTrack(Beatmap.Value.Track);
@@ -251,6 +255,38 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for player load", () => player.IsLoaded);
}
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestEpilepsyWarning(bool warning)
+ {
+ AddStep("change epilepsy warning", () => epilepsyWarning = warning);
+ AddStep("load dummy beatmap", () => ResetPlayer(false));
+
+ AddUntilStep("wait for current", () => loader.IsCurrentScreen());
+
+ AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType().Any() == warning);
+
+ if (warning)
+ {
+ AddUntilStep("sound volume decreased", () => Beatmap.Value.Track.AggregateVolume.Value == 0.25);
+ AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
+ }
+ }
+
+ [Test]
+ public void TestEpilepsyWarningEarlyExit()
+ {
+ AddStep("set epilepsy warning", () => epilepsyWarning = true);
+ AddStep("load dummy beatmap", () => ResetPlayer(false));
+
+ AddUntilStep("wait for current", () => loader.IsCurrentScreen());
+
+ AddUntilStep("wait for epilepsy warning", () => loader.ChildrenOfType().Single().Alpha > 0);
+ AddStep("exit early", () => loader.Exit());
+
+ AddUntilStep("sound volume restored", () => Beatmap.Value.Track.AggregateVolume.Value == 1);
+ }
+
private class TestPlayerLoaderContainer : Container
{
[Cached]
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
index bc1c10e59d..47dd47959d 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -12,11 +13,13 @@ using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
using osu.Framework.Testing;
using osu.Framework.Threading;
+using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
@@ -33,6 +36,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder;
+ [Cached]
+ private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
+
[SetUp]
public void SetUp() => Schedule(() =>
{
@@ -166,6 +172,12 @@ namespace osu.Game.Tests.Visual.Gameplay
playbackManager?.ReplayInputHandler.SetFrameFromTime(Time.Current - 100);
}
+ [TearDownSteps]
+ public void TearDown()
+ {
+ AddStep("stop recorder", () => recorder.Expire());
+ }
+
public class TestFramedReplayInputHandler : FramedReplayInputHandler
{
public TestFramedReplayInputHandler(Replay replay)
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
index c0f99db85d..6872b6a669 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs
@@ -2,17 +2,20 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges;
+using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Replays;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
using osu.Game.Tests.Visual.UserInterface;
using osuTK;
using osuTK.Graphics;
@@ -25,6 +28,9 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly TestRulesetInputManager recordingManager;
+ [Cached]
+ private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
+
public TestSceneReplayRecording()
{
Replay replay = new Replay();
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
new file mode 100644
index 0000000000..fec1610160
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableHUDOverlay.cs
@@ -0,0 +1,99 @@
+// 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;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.IEnumerableExtensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Configuration;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.Play;
+using osuTK.Input;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSkinnableHUDOverlay : SkinnableTestScene
+ {
+ private HUDOverlay hudOverlay;
+
+ private IEnumerable hudOverlays => CreatedDrawables.OfType();
+
+ // best way to check without exposing.
+ private Drawable hideTarget => hudOverlay.KeyCounter;
+ private FillFlowContainer keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType>().First();
+
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ [Test]
+ public void TestComboCounterIncrementing()
+ {
+ createNew();
+
+ AddRepeatStep("increase combo", () =>
+ {
+ foreach (var hud in hudOverlays)
+ hud.ComboCounter.Current.Value++;
+ }, 10);
+
+ AddStep("reset combo", () =>
+ {
+ foreach (var hud in hudOverlays)
+ hud.ComboCounter.Current.Value = 0;
+ });
+ }
+
+ [Test]
+ public void TestFadesInOnLoadComplete()
+ {
+ float? initialAlpha = null;
+
+ createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
+ AddUntilStep("wait for load", () => hudOverlay.IsAlive);
+ AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
+ }
+
+ [Test]
+ public void TestHideExternally()
+ {
+ createNew();
+
+ AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
+
+ AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
+ AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
+
+ // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
+ AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
+ }
+
+ private void createNew(Action action = null)
+ {
+ AddStep("create overlay", () =>
+ {
+ SetContents(() =>
+ {
+ hudOverlay = new HUDOverlay(null, null, null, Array.Empty());
+
+ // Add any key just to display the key counter visually.
+ hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
+
+ hudOverlay.ComboCounter.Current.Value = 1;
+
+ action?.Invoke(hudOverlay);
+
+ return hudOverlay;
+ });
+ });
+ }
+
+ protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
index 864e88d023..fc0cda2c1f 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableSound.cs
@@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay
skinSource = new TestSkinSourceContainer
{
RelativeSizeAxes = Axes.Both,
- Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("normal-sliderslide"))
+ Child = skinnableSound = new PausableSkinnableSound(new SampleInfo("Gameplay/normal-sliderslide"))
},
};
});
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
new file mode 100644
index 0000000000..1d8231cce7
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs
@@ -0,0 +1,361 @@
+// 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.Collections.Specialized;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input.Bindings;
+using osu.Framework.Input.Events;
+using osu.Framework.Input.StateChanges;
+using osu.Framework.Logging;
+using osu.Framework.Testing;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online.API;
+using osu.Game.Online.Spectator;
+using osu.Game.Replays;
+using osu.Game.Replays.Legacy;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Replays;
+using osu.Game.Rulesets.Replays.Types;
+using osu.Game.Rulesets.UI;
+using osu.Game.Screens.Play;
+using osu.Game.Tests.Visual.UserInterface;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestSceneSpectatorPlayback : OsuManualInputManagerTestScene
+ {
+ protected override bool UseOnlineAPI => true;
+
+ private TestRulesetInputManager playbackManager;
+ private TestRulesetInputManager recordingManager;
+
+ private Replay replay;
+
+ private IBindableList users;
+
+ private TestReplayRecorder recorder;
+
+ private readonly ManualClock manualClock = new ManualClock();
+
+ private OsuSpriteText latencyDisplay;
+
+ private TestFramedReplayInputHandler replayHandler;
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ [Resolved]
+ private SpectatorStreamingClient streamingClient { get; set; }
+
+ [Cached]
+ private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap());
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ replay = new Replay();
+
+ users = streamingClient.PlayingUsers.GetBoundCopy();
+ users.BindCollectionChanged((obj, args) =>
+ {
+ switch (args.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ foreach (int user in args.NewItems)
+ {
+ if (user == api.LocalUser.Value.Id)
+ streamingClient.WatchUser(user);
+ }
+
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ foreach (int user in args.OldItems)
+ {
+ if (user == api.LocalUser.Value.Id)
+ streamingClient.StopWatchingUser(user);
+ }
+
+ break;
+ }
+ }, true);
+
+ streamingClient.OnNewFrames += onNewFrames;
+
+ Add(new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ recordingManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ Recorder = recorder = new TestReplayRecorder
+ {
+ ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.Brown,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Sending",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ },
+ new Drawable[]
+ {
+ playbackManager = new TestRulesetInputManager(new TestSceneModSettings.TestRulesetInfo(), 0, SimultaneousBindingMode.Unique)
+ {
+ Clock = new FramedClock(manualClock),
+ ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay)
+ {
+ GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
+ },
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.DarkBlue,
+ RelativeSizeAxes = Axes.Both,
+ },
+ new OsuSpriteText
+ {
+ Text = "Receiving",
+ Scale = new Vector2(3),
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ },
+ new TestInputConsumer()
+ }
+ },
+ }
+ }
+ }
+ });
+
+ Add(latencyDisplay = new OsuSpriteText());
+ });
+
+ private void onNewFrames(int userId, FrameDataBundle frames)
+ {
+ Logger.Log($"Received {frames.Frames.Count()} new frames ({string.Join(',', frames.Frames.Select(f => ((int)f.Time).ToString()))})");
+
+ foreach (var legacyFrame in frames.Frames)
+ {
+ var frame = new TestReplayFrame();
+ frame.FromLegacy(legacyFrame, null, null);
+ replay.Frames.Add(frame);
+ }
+ }
+
+ [Test]
+ public void TestBasic()
+ {
+ }
+
+ private double latency = SpectatorStreamingClient.TIME_BETWEEN_SENDS;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (latencyDisplay == null) return;
+
+ // propagate initial time value
+ if (manualClock.CurrentTime == 0)
+ {
+ manualClock.CurrentTime = Time.Current;
+ return;
+ }
+
+ if (replayHandler.NextFrame != null)
+ {
+ var lastFrame = replay.Frames.LastOrDefault();
+
+ // this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved).
+ // in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation.
+ if (lastFrame != null)
+ latency = Math.Max(latency, Time.Current - lastFrame.Time);
+
+ latencyDisplay.Text = $"latency: {latency:N1}";
+
+ double proposedTime = Time.Current - latency + Time.Elapsed;
+
+ // this will either advance by one or zero frames.
+ double? time = replayHandler.SetFrameFromTime(proposedTime);
+
+ if (time == null)
+ return;
+
+ manualClock.CurrentTime = time.Value;
+ }
+ }
+
+ [TearDownSteps]
+ public void TearDown()
+ {
+ AddStep("stop recorder", () =>
+ {
+ recorder.Expire();
+ streamingClient.OnNewFrames -= onNewFrames;
+ });
+ }
+
+ public class TestFramedReplayInputHandler : FramedReplayInputHandler
+ {
+ public TestFramedReplayInputHandler(Replay replay)
+ : base(replay)
+ {
+ }
+
+ public override void CollectPendingInputs(List inputs)
+ {
+ inputs.Add(new MousePositionAbsoluteInput { Position = GamefieldToScreenSpace(CurrentFrame?.Position ?? Vector2.Zero) });
+ inputs.Add(new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() });
+ }
+ }
+
+ public class TestInputConsumer : CompositeDrawable, IKeyBindingHandler
+ {
+ public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent.ReceivePositionalInputAt(screenSpacePos);
+
+ private readonly Box box;
+
+ public TestInputConsumer()
+ {
+ Size = new Vector2(30);
+
+ Origin = Anchor.Centre;
+
+ InternalChildren = new Drawable[]
+ {
+ box = new Box
+ {
+ Colour = Color4.Black,
+ RelativeSizeAxes = Axes.Both,
+ },
+ };
+ }
+
+ protected override bool OnMouseMove(MouseMoveEvent e)
+ {
+ Position = e.MousePosition;
+ return base.OnMouseMove(e);
+ }
+
+ public bool OnPressed(TestAction action)
+ {
+ box.Colour = Color4.White;
+ return true;
+ }
+
+ public void OnReleased(TestAction action)
+ {
+ box.Colour = Color4.Black;
+ }
+ }
+
+ public class TestRulesetInputManager : RulesetInputManager
+ {
+ public TestRulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ : base(ruleset, variant, unique)
+ {
+ }
+
+ protected override KeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
+ => new TestKeyBindingContainer();
+
+ internal class TestKeyBindingContainer : KeyBindingContainer
+ {
+ public override IEnumerable DefaultKeyBindings => new[]
+ {
+ new KeyBinding(InputKey.MouseLeft, TestAction.Down),
+ };
+ }
+ }
+
+ public class TestReplayFrame : ReplayFrame, IConvertibleReplayFrame
+ {
+ public Vector2 Position;
+
+ public List Actions = new List();
+
+ public TestReplayFrame(double time, Vector2 position, params TestAction[] actions)
+ : base(time)
+ {
+ Position = position;
+ Actions.AddRange(actions);
+ }
+
+ public TestReplayFrame()
+ {
+ }
+
+ public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
+ {
+ Position = currentFrame.Position;
+ Time = currentFrame.Time;
+ if (currentFrame.MouseLeft)
+ Actions.Add(TestAction.Down);
+ }
+
+ public LegacyReplayFrame ToLegacy(IBeatmap beatmap)
+ {
+ ReplayButtonState state = ReplayButtonState.None;
+
+ if (Actions.Contains(TestAction.Down))
+ state |= ReplayButtonState.Left1;
+
+ return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
+ }
+ }
+
+ public enum TestAction
+ {
+ Down,
+ }
+
+ internal class TestReplayRecorder : ReplayRecorder
+ {
+ public TestReplayRecorder()
+ : base(new Replay())
+ {
+ }
+
+ protected override ReplayFrame HandleFrame(Vector2 mousePosition, List actions, ReplayFrame previousFrame)
+ {
+ return new TestReplayFrame(Time.Current, mousePosition, actions.ToArray());
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
index fdc20dc477..07ff56b5c3 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs
@@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public Bindable InitialRoomsReceived { get; } = new Bindable(true);
- public IBindableList Rooms { get; } = null;
+ public IBindableList Rooms => null;
public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
{
diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
index 72033fc121..0cc6e9f358 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs
@@ -3,14 +3,13 @@
using System;
using System.Collections.Generic;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Overlays.Dashboard.Friends;
-using osu.Framework.Graphics;
-using osu.Game.Users;
-using osu.Game.Overlays;
-using osu.Framework.Allocation;
using NUnit.Framework;
-using osu.Game.Online.API;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Dashboard.Friends;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
@@ -36,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOffline()
{
- AddStep("Populate", () => display.Users = getUsers());
+ AddStep("Populate with offline test users", () => display.Users = getUsers());
}
[Test]
@@ -80,14 +79,7 @@ namespace osu.Game.Tests.Visual.Online
private class TestFriendDisplay : FriendDisplay
{
- public void Fetch()
- {
- base.APIStateChanged(API, APIState.Online);
- }
-
- public override void APIStateChanged(IAPIProvider api, APIState state)
- {
- }
+ public void Fetch() => PerformFetch();
}
}
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
index 9591d53b24..ec183adbbc 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneOnlineViewContainer.cs
@@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOnlineStateVisibility()
{
- AddStep("set status to online", () => ((DummyAPIAccess)API).State = APIState.Online);
+ AddStep("set status to online", () => ((DummyAPIAccess)API).SetState(APIState.Online));
AddUntilStep("children are visible", () => onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
@@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOfflineStateVisibility()
{
- AddStep("set status to offline", () => ((DummyAPIAccess)API).State = APIState.Offline);
+ AddStep("set status to offline", () => ((DummyAPIAccess)API).SetState(APIState.Offline));
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
@@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestConnectingStateVisibility()
{
- AddStep("set status to connecting", () => ((DummyAPIAccess)API).State = APIState.Connecting);
+ AddStep("set status to connecting", () => ((DummyAPIAccess)API).SetState(APIState.Connecting));
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);
@@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestFailingStateVisibility()
{
- AddStep("set status to failing", () => ((DummyAPIAccess)API).State = APIState.Failing);
+ AddStep("set status to failing", () => ((DummyAPIAccess)API).SetState(APIState.Failing));
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
index 1e87893f39..2af15923a0 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
@@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Ranking
}
}
},
- new AccuracyCircle(score)
+ new AccuracyCircle(score, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
index 250fdc5ebd..5af55e99f8 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs
@@ -103,7 +103,7 @@ namespace osu.Game.Tests.Visual.Ranking
private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () =>
{
- Child = panel = new ScorePanel(score)
+ Child = panel = new ScorePanel(score, true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs
index d12f32e470..d0067c3396 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneStarRatingDisplay.cs
@@ -18,13 +18,13 @@ namespace osu.Game.Tests.Visual.Ranking
Origin = Anchor.Centre,
Children = new Drawable[]
{
- new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 1.23 }),
- new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 2.34 }),
- new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 3.45 }),
- new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 4.56 }),
- new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 5.67 }),
- new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 6.78 }),
- new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 10.11 }),
+ new StarRatingDisplay(new StarDifficulty(1.23, 0)),
+ new StarRatingDisplay(new StarDifficulty(2.34, 0)),
+ new StarRatingDisplay(new StarDifficulty(3.45, 0)),
+ new StarRatingDisplay(new StarDifficulty(4.56, 0)),
+ new StarRatingDisplay(new StarDifficulty(5.67, 0)),
+ new StarRatingDisplay(new StarDifficulty(6.78, 0)),
+ new StarRatingDisplay(new StarDifficulty(10.11, 0)),
}
};
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
index 8669235a7a..4699784327 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs
@@ -11,6 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets;
@@ -40,6 +41,12 @@ namespace osu.Game.Tests.Visual.SongSelect
this.rulesets = rulesets;
}
+ [Test]
+ public void TestManyPanels()
+ {
+ loadBeatmaps(count: 5000, randomDifficulties: true);
+ }
+
[Test]
public void TestKeyRepeat()
{
@@ -707,21 +714,22 @@ namespace osu.Game.Tests.Visual.SongSelect
checkVisibleItemCount(true, 15);
}
- private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null)
+ private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, bool randomDifficulties = false)
{
- createCarousel(carouselAdjust);
-
- if (beatmapSets == null)
- {
- beatmapSets = new List();
-
- for (int i = 1; i <= set_count; i++)
- beatmapSets.Add(createTestBeatmapSet(i));
- }
-
bool changed = false;
- AddStep($"Load {(beatmapSets.Count > 0 ? beatmapSets.Count.ToString() : "some")} beatmaps", () =>
+
+ createCarousel(c =>
{
+ carouselAdjust?.Invoke(c);
+
+ if (beatmapSets == null)
+ {
+ beatmapSets = new List();
+
+ for (int i = 1; i <= (count ?? set_count); i++)
+ beatmapSets.Add(createTestBeatmapSet(i, randomDifficulties));
+ }
+
carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
carousel.BeatmapSetsChanged = () => changed = true;
carousel.BeatmapSets = beatmapSets;
@@ -807,7 +815,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private bool selectedBeatmapVisible()
{
- var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
+ var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
if (currentlySelected == null)
return true;
@@ -820,7 +828,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("Selection is visible", selectedBeatmapVisible);
}
- private BeatmapSetInfo createTestBeatmapSet(int id)
+ private BeatmapSetInfo createTestBeatmapSet(int id, bool randomDifficultyCount = false)
{
return new BeatmapSetInfo
{
@@ -834,42 +842,37 @@ namespace osu.Game.Tests.Visual.SongSelect
Title = $"test set #{id}!",
AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5))
},
- Beatmaps = new List(new[]
- {
- new BeatmapInfo
- {
- OnlineBeatmapID = id * 10,
- Version = "Normal",
- StarDifficulty = 2,
- BaseDifficulty = new BeatmapDifficulty
- {
- OverallDifficulty = 3.5f,
- }
- },
- new BeatmapInfo
- {
- OnlineBeatmapID = id * 10 + 1,
- Version = "Hard",
- StarDifficulty = 5,
- BaseDifficulty = new BeatmapDifficulty
- {
- OverallDifficulty = 5,
- }
- },
- new BeatmapInfo
- {
- OnlineBeatmapID = id * 10 + 2,
- Version = "Insane",
- StarDifficulty = 6,
- BaseDifficulty = new BeatmapDifficulty
- {
- OverallDifficulty = 7,
- }
- },
- }),
+ Beatmaps = getBeatmaps(randomDifficultyCount ? RNG.Next(1, 20) : 3).ToList()
};
}
+ private IEnumerable getBeatmaps(int count)
+ {
+ int id = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+ float diff = (float)i / count * 10;
+
+ string version = "Normal";
+ if (diff > 6.6)
+ version = "Insane";
+ else if (diff > 3.3)
+ version = "Hard";
+
+ yield return new BeatmapInfo
+ {
+ OnlineBeatmapID = id++ * 10,
+ Version = version,
+ StarDifficulty = diff,
+ BaseDifficulty = new BeatmapDifficulty
+ {
+ OverallDifficulty = diff,
+ }
+ };
+ }
+ }
+
private BeatmapSetInfo createTestBeatmapSetWithManyDifficulties(int id)
{
var toReturn = new BeatmapSetInfo
@@ -908,10 +911,25 @@ namespace osu.Game.Tests.Visual.SongSelect
private class TestBeatmapCarousel : BeatmapCarousel
{
- public new List Items => base.Items;
-
public bool PendingFilterTask => PendingFilter != null;
+ public IEnumerable Items
+ {
+ get
+ {
+ foreach (var item in ScrollableContent)
+ {
+ yield return item;
+
+ if (item is DrawableCarouselBeatmapSet set)
+ {
+ foreach (var difficulty in set.DrawableBeatmaps)
+ yield return difficulty;
+ }
+ }
+ }
+ }
+
protected override IEnumerable GetLoadableBeatmaps() => Enumerable.Empty();
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
index e02ebf3be1..0b2c0ce63b 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs
@@ -197,8 +197,8 @@ namespace osu.Game.Tests.Visual.SongSelect
private class TestHitObject : ConvertHitObject, IHasPosition
{
- public float X { get; } = 0;
- public float Y { get; } = 0;
+ public float X => 0;
+ public float Y => 0;
public Vector2 Position { get; } = Vector2.Zero;
}
}
diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
index 0299b7a084..cd97ffe9e7 100644
--- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
+++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs
@@ -507,7 +507,7 @@ namespace osu.Game.Tests.Visual.SongSelect
var selectedPanel = songSelect.Carousel.ChildrenOfType().First(s => s.Item.State.Value == CarouselItemState.Selected);
// special case for converts checked here.
- return selectedPanel.ChildrenOfType().All(i =>
+ return selectedPanel.ChildrenOfType().All(i =>
i.IsFiltered || i.Item.Beatmap.Ruleset.ID == targetRuleset || i.Item.Beatmap.Ruleset.ID == 0);
});
@@ -606,10 +606,10 @@ namespace osu.Game.Tests.Visual.SongSelect
set = songSelect.Carousel.ChildrenOfType().First();
});
- DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null;
+ FilterableDifficultyIcon difficultyIcon = null;
AddStep("Find an icon", () =>
{
- difficultyIcon = set.ChildrenOfType()
+ difficultyIcon = set.ChildrenOfType()
.First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex());
});
@@ -634,13 +634,13 @@ namespace osu.Game.Tests.Visual.SongSelect
}));
BeatmapInfo filteredBeatmap = null;
- DrawableCarouselBeatmapSet.FilterableDifficultyIcon filteredIcon = null;
+ FilterableDifficultyIcon filteredIcon = null;
AddStep("Get filtered icon", () =>
{
filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First(b => b.BPM < maxBPM);
int filteredBeatmapIndex = getBeatmapIndex(filteredBeatmap.BeatmapSet, filteredBeatmap);
- filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex);
+ filteredIcon = set.ChildrenOfType().ElementAt(filteredBeatmapIndex);
});
AddStep("Click on a filtered difficulty", () =>
@@ -674,10 +674,10 @@ namespace osu.Game.Tests.Visual.SongSelect
return set != null;
});
- DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null;
+ FilterableDifficultyIcon difficultyIcon = null;
AddStep("Find an icon for different ruleset", () =>
{
- difficultyIcon = set.ChildrenOfType()
+ difficultyIcon = set.ChildrenOfType()
.First(icon => icon.Item.Beatmap.Ruleset.ID == 3);
});
@@ -725,10 +725,10 @@ namespace osu.Game.Tests.Visual.SongSelect
return set != null;
});
- DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon groupIcon = null;
+ FilterableGroupedDifficultyIcon groupIcon = null;
AddStep("Find group icon for different ruleset", () =>
{
- groupIcon = set.ChildrenOfType()
+ groupIcon = set.ChildrenOfType()
.First(icon => icon.Items.First().Beatmap.Ruleset.ID == 3);
});
@@ -821,9 +821,9 @@ namespace osu.Game.Tests.Visual.SongSelect
private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap);
- private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, DrawableCarouselBeatmapSet.FilterableDifficultyIcon icon)
+ private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon)
{
- return set.ChildrenOfType().ToList().FindIndex(i => i == icon);
+ return set.ChildrenOfType().ToList().FindIndex(i => i == icon);
}
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
index a4698a9a32..3f757031f8 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.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.Allocation;
using osu.Framework.Graphics;
@@ -27,6 +28,9 @@ namespace osu.Game.Tests.Visual.UserInterface
OsuSpriteText category;
OsuSpriteText genre;
OsuSpriteText language;
+ OsuSpriteText extra;
+ OsuSpriteText ranks;
+ OsuSpriteText played;
Add(control = new BeatmapListingSearchControl
{
@@ -46,6 +50,9 @@ namespace osu.Game.Tests.Visual.UserInterface
category = new OsuSpriteText(),
genre = new OsuSpriteText(),
language = new OsuSpriteText(),
+ extra = new OsuSpriteText(),
+ ranks = new OsuSpriteText(),
+ played = new OsuSpriteText()
}
});
@@ -54,6 +61,9 @@ namespace osu.Game.Tests.Visual.UserInterface
control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true);
control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true);
control.Language.BindValueChanged(l => language.Text = $"Language: {l.NewValue}", true);
+ control.Extra.BindCollectionChanged((u, v) => extra.Text = $"Extra: {(control.Extra.Any() ? string.Join('.', control.Extra.Select(i => i.ToString().ToLowerInvariant())) : "")}", true);
+ control.Ranks.BindCollectionChanged((u, v) => ranks.Text = $"Ranks: {(control.Ranks.Any() ? string.Join('.', control.Ranks.Select(i => i.ToString())) : "")}", true);
+ control.Played.BindValueChanged(p => played.Text = $"Played: {p.NewValue}", true);
}
[Test]
diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs
index 8c85c9a46f..75991a1ab8 100644
--- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs
+++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs
@@ -4,19 +4,24 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Tournament.Models;
+using osuTK;
namespace osu.Game.Tournament.Components
{
- public class DrawableTeamFlag : Sprite
+ public class DrawableTeamFlag : Container
{
private readonly TournamentTeam team;
[UsedImplicitly]
private Bindable flag;
+ private Sprite flagSprite;
+
public DrawableTeamFlag(TournamentTeam team)
{
this.team = team;
@@ -27,7 +32,18 @@ namespace osu.Game.Tournament.Components
{
if (team == null) return;
- (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => Texture = textures.Get($@"Flags/{team.FlagName}"), true);
+ Size = new Vector2(75, 50);
+ Masking = true;
+ CornerRadius = 5;
+ Child = flagSprite = new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ FillMode = FillMode.Fill
+ };
+
+ (flag = team.FlagName.GetBoundCopy()).BindValueChanged(acronym => flagSprite.Texture = textures.Get($@"Flags/{team.FlagName}"), true);
}
}
}
diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
index f8aed26ce1..b9442a67f5 100644
--- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
+++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs
@@ -4,9 +4,7 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Tournament.Models;
@@ -17,7 +15,7 @@ namespace osu.Game.Tournament.Components
{
public readonly TournamentTeam Team;
- protected readonly Sprite Flag;
+ protected readonly Container Flag;
protected readonly TournamentSpriteText AcronymText;
[UsedImplicitly]
@@ -27,12 +25,7 @@ namespace osu.Game.Tournament.Components
{
Team = team;
- Flag = new DrawableTeamFlag(team)
- {
- RelativeSizeAxes = Axes.Both,
- FillMode = FillMode.Fit
- };
-
+ Flag = new DrawableTeamFlag(team);
AcronymText = new TournamentSpriteText
{
Font = OsuFont.Torus.With(weight: FontWeight.Regular),
diff --git a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs
index 4f0ce0bbe7..cd252392ba 100644
--- a/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs
+++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupTeam.cs
@@ -27,6 +27,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
AcronymText.Origin = Anchor.TopCentre;
AcronymText.Text = team.Acronym.Value.ToUpperInvariant();
AcronymText.Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 10);
+ Flag.Scale = new Vector2(0.48f);
InternalChildren = new Drawable[]
{
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs
index 1b4a769b84..4ba86dcefc 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs
@@ -29,7 +29,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
var anchor = flip ? Anchor.TopLeft : Anchor.TopRight;
Flag.RelativeSizeAxes = Axes.None;
- Flag.Size = new Vector2(60, 40);
+ Flag.Scale = new Vector2(0.8f);
Flag.Origin = anchor;
Flag.Anchor = anchor;
diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs
index 15cb7e44cb..bb1e4d2eff 100644
--- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs
+++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
this.losers = losers;
Size = new Vector2(150, 40);
- Flag.Scale = new Vector2(0.9f);
+ Flag.Scale = new Vector2(0.54f);
Flag.Anchor = Flag.Origin = Anchor.CentreLeft;
AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft;
diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
index b343608e69..55fc80dba2 100644
--- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
+++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs
@@ -288,8 +288,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
AutoSizeAxes = Axes.Both;
Flag.RelativeSizeAxes = Axes.None;
- Flag.Size = new Vector2(300, 200);
- Flag.Scale = new Vector2(0.3f);
+ Flag.Scale = new Vector2(1.2f);
InternalChild = new FillFlowContainer
{
diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs
index 3870f486e1..7ca262a2e8 100644
--- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs
+++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs
@@ -90,11 +90,10 @@ namespace osu.Game.Tournament.Screens.TeamWin
{
new DrawableTeamFlag(match.Winner)
{
- Size = new Vector2(300, 200),
- Scale = new Vector2(0.5f),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Position = new Vector2(-300, 10),
+ Scale = new Vector2(2f)
},
new FillFlowContainer
{
diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs
index 8b1f5a366a..8efaeb3795 100644
--- a/osu.Game/Audio/HitSampleInfo.cs
+++ b/osu.Game/Audio/HitSampleInfo.cs
@@ -50,9 +50,9 @@ namespace osu.Game.Audio
get
{
if (!string.IsNullOrEmpty(Suffix))
- yield return $"{Bank}-{Name}{Suffix}";
+ yield return $"Gameplay/{Bank}-{Name}{Suffix}";
- yield return $"{Bank}-{Name}";
+ yield return $"Gameplay/{Bank}-{Name}";
}
}
diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs
index 2406b0bef2..240d70c418 100644
--- a/osu.Game/Audio/SampleInfo.cs
+++ b/osu.Game/Audio/SampleInfo.cs
@@ -10,14 +10,14 @@ namespace osu.Game.Audio
///
public class SampleInfo : ISampleInfo
{
- private readonly string sampleName;
+ private readonly string[] sampleNames;
- public SampleInfo(string sampleName)
+ public SampleInfo(params string[] sampleNames)
{
- this.sampleName = sampleName;
+ this.sampleNames = sampleNames;
}
- public IEnumerable LookupNames => new[] { sampleName };
+ public IEnumerable LookupNames => sampleNames;
public int Volume { get; } = 100;
}
diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs
index 8d1f0e59bf..ffd8d14048 100644
--- a/osu.Game/Beatmaps/BeatmapInfo.cs
+++ b/osu.Game/Beatmaps/BeatmapInfo.cs
@@ -92,6 +92,7 @@ namespace osu.Game.Beatmaps
public bool LetterboxInBreaks { get; set; }
public bool WidescreenStoryboard { get; set; }
+ public bool EpilepsyWarning { get; set; }
// Editor
// This bookmarks stuff is necessary because DB doesn't know how to store int[]
diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
index cb4884aa51..c4563d5844 100644
--- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
@@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps
if (checkLocalCache(set, beatmap))
return;
- if (api?.State != APIState.Online)
+ if (api?.State.Value != APIState.Online)
return;
var req = new GetBeatmapRequest(beatmap);
diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
index 45327d4514..a1d5e33d1e 100644
--- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
+++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs
@@ -47,7 +47,10 @@ namespace osu.Game.Beatmaps.Drawables
private readonly IReadOnlyList mods;
private readonly bool shouldShowTooltip;
- private readonly IBindable difficultyBindable = new Bindable();
+
+ private readonly bool performBackgroundDifficultyLookup;
+
+ private readonly Bindable difficultyBindable = new Bindable();
private Drawable background;
@@ -70,10 +73,12 @@ namespace osu.Game.Beatmaps.Drawables
///
/// The beatmap to show the difficulty of.
/// Whether to display a tooltip when hovered.
- public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true)
+ /// Whether to perform difficulty lookup (including calculation if necessary).
+ public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
{
this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));
this.shouldShowTooltip = shouldShowTooltip;
+ this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup;
AutoSizeAxes = Axes.Both;
@@ -112,9 +117,13 @@ namespace osu.Game.Beatmaps.Drawables
// 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 }
},
- new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0),
};
+ if (performBackgroundDifficultyLookup)
+ iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0));
+ else
+ difficultyBindable.Value = new StarDifficulty(beatmap.StarDifficulty, 0);
+
difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating));
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 6dadbbd2da..442be6e837 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -175,6 +175,10 @@ namespace osu.Game.Beatmaps.Formats
case @"WidescreenStoryboard":
beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1;
break;
+
+ case @"EpilepsyWarning":
+ beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
+ break;
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
index 80a4d6dea4..80fd6c22bb 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs
@@ -417,7 +417,7 @@ namespace osu.Game.Beatmaps.Formats
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
int volume = samples.FirstOrDefault()?.Volume ?? 100;
- sb.Append(":");
+ sb.Append(':');
sb.Append(FormattableString.Invariant($"{customSampleBank}:"));
sb.Append(FormattableString.Invariant($"{volume}:"));
sb.Append(FormattableString.Invariant($"{sampleFilename}"));
diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
index 269449ef80..8d8ca523d5 100644
--- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs
@@ -48,6 +48,10 @@ namespace osu.Game.Beatmaps.Formats
switch (section)
{
+ case Section.General:
+ handleGeneral(storyboard, line);
+ return;
+
case Section.Events:
handleEvents(line);
return;
@@ -60,6 +64,18 @@ namespace osu.Game.Beatmaps.Formats
base.ParseLine(storyboard, section, line);
}
+ private void handleGeneral(Storyboard storyboard, string line)
+ {
+ var pair = SplitKeyVal(line);
+
+ switch (pair.Key)
+ {
+ case "UseSkinSprites":
+ storyboard.UseSkinSprites = pair.Value == "1";
+ break;
+ }
+ }
+
private void handleEvents(string line)
{
var depth = 0;
@@ -331,7 +347,7 @@ namespace osu.Game.Beatmaps.Formats
/// The line which may contains variables.
private void decodeVariables(ref string line)
{
- while (line.IndexOf('$') >= 0)
+ while (line.Contains('$'))
{
string origLine = line;
diff --git a/osu.Game/Configuration/HUDVisibilityMode.cs b/osu.Game/Configuration/HUDVisibilityMode.cs
new file mode 100644
index 0000000000..10f3f65355
--- /dev/null
+++ b/osu.Game/Configuration/HUDVisibilityMode.cs
@@ -0,0 +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.ComponentModel;
+
+namespace osu.Game.Configuration
+{
+ public enum HUDVisibilityMode
+ {
+ Never,
+
+ [Description("Hide during gameplay")]
+ HideDuringGameplay,
+
+ Always
+ }
+}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 78179a781a..e0971d238a 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -90,7 +90,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.HitLighting, true);
- Set(OsuSetting.ShowInterface, true);
+ Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
Set(OsuSetting.ShowProgressGraph, true);
Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
Set(OsuSetting.FadePlayfieldWhenHealthLow, true);
@@ -131,6 +131,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin);
+ Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes);
}
public OsuConfigManager(Storage storage)
@@ -170,6 +171,7 @@ namespace osu.Game.Configuration
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")),
+ new TrackedSetting(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription())),
new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
};
}
@@ -190,7 +192,7 @@ namespace osu.Game.Configuration
AlwaysPlayFirstComboBreak,
ScoreMeter,
FloatingComments,
- ShowInterface,
+ HUDVisibilityMode,
ShowProgressGraph,
ShowHealthDisplayWhenCantFail,
FadePlayfieldWhenHealthLow,
@@ -239,5 +241,6 @@ namespace osu.Game.Configuration
HitLighting,
MenuBackgroundSource,
GameplayDisableWinKey,
+ SeasonalBackgroundMode
}
}
diff --git a/osu.Game/Configuration/SeasonalBackgroundMode.cs b/osu.Game/Configuration/SeasonalBackgroundMode.cs
new file mode 100644
index 0000000000..6ef835ce5f
--- /dev/null
+++ b/osu.Game/Configuration/SeasonalBackgroundMode.cs
@@ -0,0 +1,23 @@
+// 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.Configuration
+{
+ public enum SeasonalBackgroundMode
+ {
+ ///
+ /// Seasonal backgrounds are shown regardless of season, if at all available.
+ ///
+ Always,
+
+ ///
+ /// Seasonal backgrounds are shown only during their corresponding season.
+ ///
+ Sometimes,
+
+ ///
+ /// Seasonal backgrounds are never shown.
+ ///
+ Never
+ }
+}
diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs
index 40b2adb867..03bc434aac 100644
--- a/osu.Game/Configuration/SessionStatics.cs
+++ b/osu.Game/Configuration/SessionStatics.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 osu.Game.Online.API.Requests.Responses;
+
namespace osu.Game.Configuration
{
///
@@ -12,12 +14,19 @@ namespace osu.Game.Configuration
{
Set(Static.LoginOverlayDisplayed, false);
Set(Static.MutedAudioNotificationShownOnce, false);
+ Set(Static.SeasonalBackgrounds, null);
}
}
public enum Static
{
LoginOverlayDisplayed,
- MutedAudioNotificationShownOnce
+ MutedAudioNotificationShownOnce,
+
+ ///
+ /// Info about seasonal backgrounds available fetched from API - see .
+ /// Value under this lookup can be null if there are no backgrounds available (or API is not reachable).
+ ///
+ SeasonalBackgrounds,
}
}
diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs
index 1fd2f23d50..ddafd77066 100644
--- a/osu.Game/Database/DatabaseWriteUsage.cs
+++ b/osu.Game/Database/DatabaseWriteUsage.cs
@@ -26,7 +26,7 @@ namespace osu.Game.Database
/// Whether this write usage will commit a transaction on completion.
/// If false, there is a parent usage responsible for transaction commit.
///
- public bool IsTransactionLeader = false;
+ public bool IsTransactionLeader;
protected void Dispose(bool disposing)
{
diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs
new file mode 100644
index 0000000000..a48da37804
--- /dev/null
+++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs
@@ -0,0 +1,103 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Utils;
+using osu.Game.Configuration;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Online.API.Requests.Responses;
+
+namespace osu.Game.Graphics.Backgrounds
+{
+ public class SeasonalBackgroundLoader : Component
+ {
+ ///
+ /// Fired when background should be changed due to receiving backgrounds from API
+ /// or when the user setting is changed (as it might require unloading the seasonal background).
+ ///
+ public event Action SeasonalBackgroundChanged;
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ private readonly IBindable apiState = new Bindable();
+ private Bindable seasonalBackgroundMode;
+ private Bindable seasonalBackgrounds;
+
+ private int current;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config, SessionStatics sessionStatics)
+ {
+ seasonalBackgroundMode = config.GetBindable(OsuSetting.SeasonalBackgroundMode);
+ seasonalBackgroundMode.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke());
+
+ seasonalBackgrounds = sessionStatics.GetBindable(Static.SeasonalBackgrounds);
+ seasonalBackgrounds.BindValueChanged(_ => SeasonalBackgroundChanged?.Invoke());
+
+ apiState.BindTo(api.State);
+ apiState.BindValueChanged(fetchSeasonalBackgrounds, true);
+ }
+
+ private void fetchSeasonalBackgrounds(ValueChangedEvent stateChanged)
+ {
+ if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online)
+ return;
+
+ var request = new GetSeasonalBackgroundsRequest();
+ request.Success += response =>
+ {
+ seasonalBackgrounds.Value = response;
+ current = RNG.Next(0, response.Backgrounds?.Count ?? 0);
+ };
+
+ api.PerformAsync(request);
+ }
+
+ public SeasonalBackground LoadNextBackground()
+ {
+ if (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Never
+ || (seasonalBackgroundMode.Value == SeasonalBackgroundMode.Sometimes && !isInSeason))
+ {
+ return null;
+ }
+
+ var backgrounds = seasonalBackgrounds.Value?.Backgrounds;
+ if (backgrounds == null || !backgrounds.Any())
+ return null;
+
+ current = (current + 1) % backgrounds.Count;
+ string url = backgrounds[current].Url;
+
+ return new SeasonalBackground(url);
+ }
+
+ private bool isInSeason => seasonalBackgrounds.Value != null && DateTimeOffset.Now < seasonalBackgrounds.Value.EndDate;
+ }
+
+ [LongRunningLoad]
+ public class SeasonalBackground : Background
+ {
+ private readonly string url;
+ private const string fallback_texture_name = @"Backgrounds/bg1";
+
+ public SeasonalBackground(string url)
+ {
+ this.url = url;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(LargeTextureStore textures)
+ {
+ Sprite.Texture = textures.Get(url) ?? textures.Get(fallback_texture_name);
+ // ensure we're not loading in without a transition.
+ this.FadeInFromZero(200, Easing.InOutSine);
+ }
+ }
+}
diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
index ed5c73bee6..b9122d254d 100644
--- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs
+++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs
@@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers
/// Allows controlling the scroll bar from any position in the container using the right mouse button.
/// Uses the value of to smoothly scroll to the dragged location.
///
- public bool RightMouseScrollbar = false;
+ public bool RightMouseScrollbar;
///
/// Controls the rate with which the target position is approached when performing a relative drag. Default is 0.02.
diff --git a/osu.Game/Graphics/UserInterface/LoadingLayer.cs b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
index 35b33c3d03..c8c4424bee 100644
--- a/osu.Game/Graphics/UserInterface/LoadingLayer.cs
+++ b/osu.Game/Graphics/UserInterface/LoadingLayer.cs
@@ -44,6 +44,11 @@ namespace osu.Game.Graphics.UserInterface
// blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer.
case ScrollEvent _:
return false;
+
+ // blocking touch events causes the ISourcedFromTouch versions to not be fired, potentially impeding behaviour of drawables *above* the loading layer that may utilise these.
+ // note that this will not work well if touch handling elements are beneath this loading layer (something to consider for the future).
+ case TouchEvent _:
+ return false;
}
return true;
diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs
index a30f961daf..f74574e60c 100644
--- a/osu.Game/IO/Archives/ArchiveReader.cs
+++ b/osu.Game/IO/Archives/ArchiveReader.cs
@@ -41,7 +41,7 @@ namespace osu.Game.IO.Archives
return null;
byte[] buffer = new byte[input.Length];
- await input.ReadAsync(buffer, 0, buffer.Length);
+ await input.ReadAsync(buffer);
return buffer;
}
}
diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs
index 41be4cfcc3..3de4bb1f9d 100644
--- a/osu.Game/Input/Bindings/GlobalActionContainer.cs
+++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs
@@ -67,6 +67,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed),
new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay),
+ new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD),
};
public IEnumerable AudioControlKeyBindings => new[]
@@ -187,5 +188,8 @@ namespace osu.Game.Input.Bindings
[Description("Timing Mode")]
EditorTimingMode,
+
+ [Description("Hold for HUD")]
+ HoldForHUD,
}
}
diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs
new file mode 100644
index 0000000000..1c05de832e
--- /dev/null
+++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs
@@ -0,0 +1,508 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using osu.Game.Database;
+
+namespace osu.Game.Migrations
+{
+ [DbContext(typeof(OsuDbContext))]
+ [Migration("20201019224408_AddEpilepsyWarning")]
+ partial class AddEpilepsyWarning
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("ApproachRate");
+
+ b.Property("CircleSize");
+
+ b.Property("DrainRate");
+
+ b.Property("OverallDifficulty");
+
+ b.Property("SliderMultiplier");
+
+ b.Property("SliderTickRate");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapDifficulty");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("AudioLeadIn");
+
+ b.Property("BPM");
+
+ b.Property("BaseDifficultyID");
+
+ b.Property("BeatDivisor");
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("Countdown");
+
+ b.Property("DistanceSpacing");
+
+ b.Property("EpilepsyWarning");
+
+ b.Property("GridSize");
+
+ b.Property("Hash");
+
+ b.Property("Hidden");
+
+ b.Property("Length");
+
+ b.Property("LetterboxInBreaks");
+
+ b.Property("MD5Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapID");
+
+ b.Property("Path");
+
+ b.Property("RulesetID");
+
+ b.Property("SpecialStyle");
+
+ b.Property("StackLeniency");
+
+ b.Property("StarDifficulty");
+
+ b.Property("Status");
+
+ b.Property("StoredBookmarks");
+
+ b.Property("TimelineZoom");
+
+ b.Property("Version");
+
+ b.Property("WidescreenStoryboard");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BaseDifficultyID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("Hash");
+
+ b.HasIndex("MD5Hash");
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("BeatmapInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Artist");
+
+ b.Property("ArtistUnicode");
+
+ b.Property("AudioFile");
+
+ b.Property("AuthorString")
+ .HasColumnName("Author");
+
+ b.Property("BackgroundFile");
+
+ b.Property("PreviewTime");
+
+ b.Property("Source");
+
+ b.Property("Tags");
+
+ b.Property("Title");
+
+ b.Property("TitleUnicode");
+
+ b.Property("VideoFile");
+
+ b.HasKey("ID");
+
+ b.ToTable("BeatmapMetadata");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("BeatmapSetInfoID");
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapSetInfoID");
+
+ b.HasIndex("FileInfoID");
+
+ b.ToTable("BeatmapSetFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("DateAdded");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MetadataID");
+
+ b.Property("OnlineBeatmapSetID");
+
+ b.Property("Protected");
+
+ b.Property("Status");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("MetadataID");
+
+ b.HasIndex("OnlineBeatmapSetID")
+ .IsUnique();
+
+ b.ToTable("BeatmapSetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Key")
+ .HasColumnName("Key");
+
+ b.Property("RulesetID");
+
+ b.Property("SkinInfoID");
+
+ b.Property("StringValue")
+ .HasColumnName("Value");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("Settings");
+ });
+
+ modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Hash");
+
+ b.Property("ReferenceCount");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.HasIndex("ReferenceCount");
+
+ b.ToTable("FileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("IntAction")
+ .HasColumnName("Action");
+
+ b.Property("KeysString")
+ .HasColumnName("Keys");
+
+ b.Property("RulesetID");
+
+ b.Property("Variant");
+
+ b.HasKey("ID");
+
+ b.HasIndex("IntAction");
+
+ b.HasIndex("RulesetID", "Variant");
+
+ b.ToTable("KeyBinding");
+ });
+
+ modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Available");
+
+ b.Property("InstantiationInfo");
+
+ b.Property("Name");
+
+ b.Property("ShortName");
+
+ b.HasKey("ID");
+
+ b.HasIndex("Available");
+
+ b.HasIndex("ShortName")
+ .IsUnique();
+
+ b.ToTable("RulesetInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("ScoreInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("ScoreInfoID");
+
+ b.ToTable("ScoreFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Accuracy")
+ .HasColumnType("DECIMAL(1,4)");
+
+ b.Property("BeatmapInfoID");
+
+ b.Property("Combo");
+
+ b.Property("Date");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("MaxCombo");
+
+ b.Property("ModsJson")
+ .HasColumnName("Mods");
+
+ b.Property("OnlineScoreID");
+
+ b.Property("PP");
+
+ b.Property("Rank");
+
+ b.Property("RulesetID");
+
+ b.Property("StatisticsJson")
+ .HasColumnName("Statistics");
+
+ b.Property("TotalScore");
+
+ b.Property("UserID")
+ .HasColumnName("UserID");
+
+ b.Property("UserString")
+ .HasColumnName("User");
+
+ b.HasKey("ID");
+
+ b.HasIndex("BeatmapInfoID");
+
+ b.HasIndex("OnlineScoreID")
+ .IsUnique();
+
+ b.HasIndex("RulesetID");
+
+ b.ToTable("ScoreInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("FileInfoID");
+
+ b.Property("Filename")
+ .IsRequired();
+
+ b.Property("SkinInfoID");
+
+ b.HasKey("ID");
+
+ b.HasIndex("FileInfoID");
+
+ b.HasIndex("SkinInfoID");
+
+ b.ToTable("SkinFileInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
+ {
+ b.Property("ID")
+ .ValueGeneratedOnAdd();
+
+ b.Property("Creator");
+
+ b.Property("DeletePending");
+
+ b.Property("Hash");
+
+ b.Property("Name");
+
+ b.HasKey("ID");
+
+ b.HasIndex("DeletePending");
+
+ b.HasIndex("Hash")
+ .IsUnique();
+
+ b.ToTable("SkinInfo");
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
+ .WithMany()
+ .HasForeignKey("BaseDifficultyID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
+ .WithMany("Beatmaps")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("Beatmaps")
+ .HasForeignKey("MetadataID");
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
+ .WithMany("Files")
+ .HasForeignKey("BeatmapSetInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
+ .WithMany("BeatmapSets")
+ .HasForeignKey("MetadataID");
+ });
+
+ modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
+ {
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Settings")
+ .HasForeignKey("SkinInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Scoring.ScoreInfo")
+ .WithMany("Files")
+ .HasForeignKey("ScoreInfoID");
+ });
+
+ modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
+ {
+ b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
+ .WithMany("Scores")
+ .HasForeignKey("BeatmapInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
+ .WithMany()
+ .HasForeignKey("RulesetID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+
+ modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
+ {
+ b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
+ .WithMany()
+ .HasForeignKey("FileInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+
+ b.HasOne("osu.Game.Skinning.SkinInfo")
+ .WithMany("Files")
+ .HasForeignKey("SkinInfoID")
+ .OnDelete(DeleteBehavior.Cascade);
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs
new file mode 100644
index 0000000000..be6968aa5d
--- /dev/null
+++ b/osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs
@@ -0,0 +1,23 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace osu.Game.Migrations
+{
+ public partial class AddEpilepsyWarning : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "EpilepsyWarning",
+ table: "BeatmapInfo",
+ nullable: false,
+ defaultValue: false);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "EpilepsyWarning",
+ table: "BeatmapInfo");
+ }
+ }
+}
diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
index bc4fc3342d..ec4461ca56 100644
--- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
+++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs
@@ -57,6 +57,8 @@ namespace osu.Game.Migrations
b.Property("DistanceSpacing");
+ b.Property("EpilepsyWarning");
+
b.Property("GridSize");
b.Property("Hash");
diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs
index 4ea5c192fe..b916339a53 100644
--- a/osu.Game/Online/API/APIAccess.cs
+++ b/osu.Game/Online/API/APIAccess.cs
@@ -78,26 +78,8 @@ namespace osu.Game.Online.API
private void onTokenChanged(ValueChangedEvent e) => config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
- private readonly List components = new List();
-
internal new void Schedule(Action action) => base.Schedule(action);
- ///
- /// Register a component to receive API events.
- /// Fires once immediately to ensure a correct state.
- ///
- ///
- public void Register(IOnlineComponent component)
- {
- Schedule(() => components.Add(component));
- component.APIStateChanged(this, state);
- }
-
- public void Unregister(IOnlineComponent component)
- {
- Schedule(() => components.Remove(component));
- }
-
public string AccessToken => authentication.RequestAccessToken();
///
@@ -109,7 +91,7 @@ namespace osu.Game.Online.API
{
while (!cancellationToken.IsCancellationRequested)
{
- switch (State)
+ switch (State.Value)
{
case APIState.Failing:
//todo: replace this with a ping request.
@@ -131,12 +113,12 @@ namespace osu.Game.Online.API
// work to restore a connection...
if (!HasLogin)
{
- State = APIState.Offline;
+ state.Value = APIState.Offline;
Thread.Sleep(50);
continue;
}
- State = APIState.Connecting;
+ state.Value = APIState.Connecting;
// save the username at this point, if the user requested for it to be.
config.Set(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
@@ -162,20 +144,20 @@ namespace osu.Game.Online.API
failureCount = 0;
//we're connected!
- State = APIState.Online;
+ state.Value = APIState.Online;
};
if (!handleRequest(userReq))
{
- if (State == APIState.Connecting)
- State = APIState.Failing;
+ if (State.Value == APIState.Connecting)
+ state.Value = APIState.Failing;
continue;
}
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
// before actually going online.
- while (State > APIState.Offline && State < APIState.Online)
+ while (State.Value > APIState.Offline && State.Value < APIState.Online)
Thread.Sleep(500);
break;
@@ -224,7 +206,7 @@ namespace osu.Game.Online.API
public void Login(string username, string password)
{
- Debug.Assert(State == APIState.Offline);
+ Debug.Assert(State.Value == APIState.Offline);
ProvidedUsername = username;
this.password = password;
@@ -232,7 +214,7 @@ namespace osu.Game.Online.API
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
{
- Debug.Assert(State == APIState.Offline);
+ Debug.Assert(State.Value == APIState.Offline);
var req = new RegistrationRequest
{
@@ -276,7 +258,7 @@ namespace osu.Game.Online.API
req.Perform(this);
// we could still be in initialisation, at which point we don't want to say we're Online yet.
- if (IsLoggedIn) State = APIState.Online;
+ if (IsLoggedIn) state.Value = APIState.Online;
failureCount = 0;
return true;
@@ -293,27 +275,12 @@ namespace osu.Game.Online.API
}
}
- private APIState state;
+ private readonly Bindable state = new Bindable();
- public APIState State
- {
- get => state;
- private set
- {
- if (state == value)
- return;
-
- APIState oldState = state;
- state = value;
-
- log.Add($@"We just went {state}!");
- Schedule(() =>
- {
- components.ForEach(c => c.APIStateChanged(this, state));
- OnStateChange?.Invoke(oldState, state);
- });
- }
- }
+ ///
+ /// The current connectivity state of the API.
+ ///
+ public IBindable State => state;
private bool handleWebException(WebException we)
{
@@ -343,9 +310,9 @@ namespace osu.Game.Online.API
// we might try again at an api level.
return false;
- if (State == APIState.Online)
+ if (State.Value == APIState.Online)
{
- State = APIState.Failing;
+ state.Value = APIState.Failing;
flushQueue();
}
@@ -362,10 +329,6 @@ namespace osu.Game.Online.API
lock (queue) queue.Enqueue(request);
}
- public event StateChangeDelegate OnStateChange;
-
- public delegate void StateChangeDelegate(APIState oldState, APIState newState);
-
private void flushQueue(bool failOldRequests = true)
{
lock (queue)
@@ -392,7 +355,7 @@ namespace osu.Game.Online.API
// Scheduled prior to state change such that the state changed event is invoked with the correct user present
Schedule(() => LocalUser.Value = createGuestUser());
- State = APIState.Offline;
+ state.Value = APIState.Offline;
}
private static User createGuestUser() => new GuestUser();
diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs
index 46a8db31b7..780e5daa16 100644
--- a/osu.Game/Online/API/APIMod.cs
+++ b/osu.Game/Online/API/APIMod.cs
@@ -53,5 +53,13 @@ namespace osu.Game.Online.API
}
public bool Equals(IMod other) => Acronym == other?.Acronym;
+
+ public override string ToString()
+ {
+ if (Settings.Count > 0)
+ return $"{Acronym} ({string.Join(',', Settings.Select(kvp => $"{kvp.Key}:{kvp.Value}"))})";
+
+ return $"{Acronym}";
+ }
}
}
diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs
index 7800241904..e275676cea 100644
--- a/osu.Game/Online/API/DummyAPIAccess.cs
+++ b/osu.Game/Online/API/DummyAPIAccess.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.Threading;
using System.Threading.Tasks;
using osu.Framework.Bindables;
@@ -21,34 +20,25 @@ namespace osu.Game.Online.API
public Bindable Activity { get; } = new Bindable();
- public bool IsLoggedIn => State == APIState.Online;
+ public string AccessToken => "token";
+
+ public bool IsLoggedIn => State.Value == APIState.Online;
public string ProvidedUsername => LocalUser.Value.Username;
public string Endpoint => "http://localhost";
- private APIState state = APIState.Online;
-
- private readonly List components = new List();
-
///
/// Provide handling logic for an arbitrary API request.
///
public Action HandleRequest;
- public APIState State
- {
- get => state;
- set
- {
- if (state == value)
- return;
+ private readonly Bindable