diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
index 5eb5efa54c..3dd6be7307 100644
--- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index d7c116411a..0c4bfe0ed7 100644
--- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
index 89b551286b..bb0a487274 100644
--- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
index d7c116411a..0c4bfe0ed7 100644
--- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/osu.Android.props b/osu.Android.props
index c845d7f276..591e00fb27 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,10 +52,10 @@
-
+
-
+
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index 47cd39dc5a..58d67c11d9 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -116,7 +116,7 @@ namespace osu.Desktop.Updater
if (scheduleRecheck)
{
// check again in 30 minutes.
- Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30);
+ Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
}
}
@@ -141,7 +141,7 @@ namespace osu.Desktop.Updater
Activated = () =>
{
updateManager.PrepareUpdateAsync()
- .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
+ .ContinueWith(_ => updateManager.Schedule(() => game?.GracefullyExit()));
return true;
};
}
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 7a74563b2b..da8a0540f4 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
index 83d0744588..484da8e22e 100644
--- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
+++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
index b2a0912d19..6df555617b 100644
--- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
+++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
index 1d500dcc14..3252e6d912 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs
@@ -37,11 +37,13 @@ namespace osu.Game.Rulesets.Osu.Tests
private readonly BindableBool snakingIn = new BindableBool();
private readonly BindableBool snakingOut = new BindableBool();
+ private IBeatmap beatmap;
+
private const double duration_of_span = 3605;
private const double fade_in_modifier = -1200;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
- => new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
+ => new ClockBackedTestWorkingBeatmap(this.beatmap = beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
[BackgroundDependencyLoader]
private void load(RulesetConfigCache configCache)
@@ -51,8 +53,16 @@ namespace osu.Game.Rulesets.Osu.Tests
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
}
+ private Slider slider;
private DrawableSlider drawableSlider;
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ slider = null;
+ drawableSlider = null;
+ });
+
[SetUpSteps]
public override void SetUpSteps()
{
@@ -67,21 +77,19 @@ namespace osu.Game.Rulesets.Osu.Tests
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
- double startTime = hitObjects[sliderIndex].StartTime;
- addSeekStep(startTime);
- retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
+ retrieveSlider(sliderIndex);
setSnaking(true);
- ensureSnakingIn(startTime + fade_in_modifier);
+ addEnsureSnakingInSteps(() => slider.StartTime + fade_in_modifier);
for (int i = 0; i < sliderIndex; i++)
{
// non-final repeats should not snake out
- ensureNoSnakingOut(startTime, i);
+ addEnsureNoSnakingOutStep(() => slider.StartTime, i);
}
// final repeat should snake out
- ensureSnakingOut(startTime, sliderIndex);
+ addEnsureSnakingOutSteps(() => slider.StartTime, sliderIndex);
}
[TestCase(0)]
@@ -93,17 +101,15 @@ namespace osu.Game.Rulesets.Osu.Tests
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
- double startTime = hitObjects[sliderIndex].StartTime;
- addSeekStep(startTime);
- retrieveDrawableSlider((Slider)hitObjects[sliderIndex]);
+ retrieveSlider(sliderIndex);
setSnaking(false);
- ensureNoSnakingIn(startTime + fade_in_modifier);
+ addEnsureNoSnakingInSteps(() => slider.StartTime + fade_in_modifier);
for (int i = 0; i <= sliderIndex; i++)
{
// no snaking out ever, including final repeat
- ensureNoSnakingOut(startTime, i);
+ addEnsureNoSnakingOutStep(() => slider.StartTime, i);
}
}
@@ -116,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests
// repeat might have a chance to update its position depending on where in the frame its hit,
// so some leniency is allowed here instead of checking strict equality
- checkPositionChange(16600, sliderRepeat, positionAlmostSame);
+ addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionAlmostSame);
}
[Test]
@@ -126,38 +132,41 @@ namespace osu.Game.Rulesets.Osu.Tests
setSnaking(true);
base.SetUpSteps();
- checkPositionChange(16600, sliderRepeat, positionDecreased);
+ addCheckPositionChangeSteps(() => 16600, getSliderRepeat, positionDecreased);
}
- private void retrieveDrawableSlider(Slider slider) => AddUntilStep($"retrieve slider @ {slider.StartTime}", () =>
- (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
-
- private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased);
- private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame);
-
- private void ensureSnakingOut(double startTime, int repeatIndex)
+ private void retrieveSlider(int index)
{
- var repeatTime = timeAtRepeat(startTime, repeatIndex);
+ AddStep("retrieve slider at index", () => slider = (Slider)beatmap.HitObjects[index]);
+ addSeekStep(() => slider);
+ AddUntilStep("retrieve drawable slider", () =>
+ (drawableSlider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == slider)) != null);
+ }
+ private void addEnsureSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionIncreased);
+ private void addEnsureNoSnakingInSteps(Func startTime) => addCheckPositionChangeSteps(startTime, getSliderEnd, positionRemainsSame);
+
+ private void addEnsureSnakingOutSteps(Func startTime, int repeatIndex)
+ {
if (repeatIndex % 2 == 0)
- checkPositionChange(repeatTime, sliderStart, positionIncreased);
+ addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderStart, positionIncreased);
else
- checkPositionChange(repeatTime, sliderEnd, positionDecreased);
+ addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), getSliderEnd, positionDecreased);
}
- private void ensureNoSnakingOut(double startTime, int repeatIndex) =>
- checkPositionChange(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
+ private void addEnsureNoSnakingOutStep(Func startTime, int repeatIndex)
+ => addCheckPositionChangeSteps(timeAtRepeat(startTime, repeatIndex), positionAtRepeat(repeatIndex), positionRemainsSame);
- private double timeAtRepeat(double startTime, int repeatIndex) => startTime + 100 + duration_of_span * repeatIndex;
- private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)sliderStart : sliderEnd;
+ private Func timeAtRepeat(Func startTime, int repeatIndex) => () => startTime() + 100 + duration_of_span * repeatIndex;
+ private Func positionAtRepeat(int repeatIndex) => repeatIndex % 2 == 0 ? (Func)getSliderStart : getSliderEnd;
- private List sliderCurve => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
- private Vector2 sliderStart() => sliderCurve.First();
- private Vector2 sliderEnd() => sliderCurve.Last();
+ private List getSliderCurve() => ((PlaySliderBody)drawableSlider.Body.Drawable).CurrentCurve;
+ private Vector2 getSliderStart() => getSliderCurve().First();
+ private Vector2 getSliderEnd() => getSliderCurve().Last();
- private Vector2 sliderRepeat()
+ private Vector2 getSliderRepeat()
{
- var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == hitObjects[1]);
+ var drawable = Player.DrawableRuleset.Playfield.AllHitObjects.SingleOrDefault(d => d.HitObject == beatmap.HitObjects[1]);
var repeat = drawable.ChildrenOfType>().First().Children.First();
return repeat.Position;
}
@@ -167,7 +176,7 @@ namespace osu.Game.Rulesets.Osu.Tests
private bool positionDecreased(Vector2 previous, Vector2 current) => current.X < previous.X && current.Y < previous.Y;
private bool positionAlmostSame(Vector2 previous, Vector2 current) => Precision.AlmostEquals(previous, current, 1);
- private void checkPositionChange(double startTime, Func positionToCheck, Func positionAssertion)
+ private void addCheckPositionChangeSteps(Func startTime, Func positionToCheck, Func positionAssertion)
{
Vector2 previousPosition = Vector2.Zero;
@@ -176,7 +185,7 @@ namespace osu.Game.Rulesets.Osu.Tests
addSeekStep(startTime);
AddStep($"save {positionDescription} position", () => previousPosition = positionToCheck.Invoke());
- addSeekStep(startTime + 100);
+ addSeekStep(() => startTime() + 100);
AddAssert($"{positionDescription} {assertionDescription}", () =>
{
var currentPosition = positionToCheck.Invoke();
@@ -193,19 +202,21 @@ namespace osu.Game.Rulesets.Osu.Tests
});
}
- private void addSeekStep(double time)
+ private void addSeekStep(Func slider)
{
- AddStep($"seek to {time}", () => Player.GameplayClockContainer.Seek(time));
-
- AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ AddStep("seek to slider", () => Player.GameplayClockContainer.Seek(slider().StartTime));
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(slider().StartTime, Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
- protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
+ private void addSeekStep(Func time)
{
- HitObjects = hitObjects
- };
+ AddStep("seek to time", () => Player.GameplayClockContainer.Seek(time()));
+ AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time(), Player.DrawableRuleset.FrameStableClock.CurrentTime, 100));
+ }
- private readonly List hitObjects = new List
+ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap { HitObjects = createHitObjects() };
+
+ private static List createHitObjects() => new List
{
new Slider
{
diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
index 1efd19f49d..68be34d153 100644
--- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
+++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs
new file mode 100644
index 0000000000..4a3b187e83
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs
@@ -0,0 +1,16 @@
+// 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.Rulesets.Osu.Mods
+{
+ ///
+ /// Marker interface for any mod which completely hides the approach circles.
+ /// Used for incompatibility with .
+ ///
+ ///
+ /// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour.
+ ///
+ public interface IHidesApproachCircles
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs
deleted file mode 100644
index 60a5825241..0000000000
--- a/osu.Game.Rulesets.Osu/Mods/IMutateApproachCircles.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-namespace osu.Game.Rulesets.Osu.Mods
-{
- ///
- /// Any mod which affects the animation or visibility of approach circles. Should be used for incompatibility purposes.
- ///
- public interface IMutateApproachCircles
- {
- }
-}
diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs
new file mode 100644
index 0000000000..1458abfe05
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs
@@ -0,0 +1,16 @@
+// 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.Rulesets.Osu.Mods
+{
+ ///
+ /// Marker interface for any mod which requires the approach circles to be visible.
+ /// Used for incompatibility with .
+ ///
+ ///
+ /// Note that this is only a marker interface for incompatibility purposes, it does not change any gameplay behaviour.
+ ///
+ public interface IRequiresApproachCircles
+ {
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
index 526e29ad53..d832411104 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs
@@ -12,7 +12,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IMutateApproachCircles
+ public class OsuModApproachDifferent : Mod, IApplicableToDrawableHitObject, IRequiresApproachCircles
{
public override string Name => "Approach Different";
public override string Acronym => "AD";
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
- public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
+ public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
public BindableFloat Scale { get; } = new BindableFloat(4)
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
index 16b38cd0b1..9c7784a00a 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs
@@ -15,12 +15,12 @@ using osu.Game.Rulesets.Osu.Skinning;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModHidden : ModHidden, IMutateApproachCircles
+ public class OsuModHidden : ModHidden, IHidesApproachCircles
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
- public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
+ public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
index 6dfabed0df..778447e444 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs
@@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods
///
/// Adjusts the size of hit objects during their fade in animation.
///
- public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IMutateApproachCircles
+ public abstract class OsuModObjectScaleTween : ModWithVisibilityAdjustment, IHidesApproachCircles
{
public override ModType Type => ModType.Fun;
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods
protected virtual float EndScale => 1;
- public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
+ public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) };
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
index d3ca2973f0..56c246953e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs
@@ -12,7 +12,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModSpinIn : ModWithVisibilityAdjustment, IMutateApproachCircles
+ public class OsuModSpinIn : ModWithVisibilityAdjustment, IHidesApproachCircles
{
public override string Name => "Spin In";
public override string Acronym => "SI";
@@ -21,8 +21,9 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Circles spin in. No approach circles.";
public override double ScoreMultiplier => 1;
- // todo: this mod should be able to be compatible with hidden with a bit of further implementation.
- public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
+ // todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque,
+ // further implementation will be required for supporting that.
+ public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) };
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;
diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
index 84263221a7..a05e4dea03 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs
@@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu.Skinning.Default;
namespace osu.Game.Rulesets.Osu.Mods
{
- public class OsuModTraceable : ModWithVisibilityAdjustment, IMutateApproachCircles
+ public class OsuModTraceable : ModWithVisibilityAdjustment, IRequiresApproachCircles
{
public override string Name => "Traceable";
public override string Acronym => "TC";
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;
- public override Type[] IncompatibleMods => new[] { typeof(IMutateApproachCircles) };
+ public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
{
diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
index 8fb167ba10..532fdc5cb0 100644
--- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
+++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
index adc1d6aede..0983b806e2 100644
--- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Rooms;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
@@ -50,7 +51,10 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("create room initially in gameplay", () =>
{
- Room.RoomID.Value = null;
+ var newRoom = new Room();
+ newRoom.CopyFrom(SelectedRoom.Value);
+
+ newRoom.RoomID.Value = null;
Client.RoomSetupAction = room =>
{
room.State = MultiplayerRoomState.Playing;
@@ -61,7 +65,7 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
});
};
- RoomManager.CreateRoom(Room);
+ RoomManager.CreateRoom(newRoom);
});
AddUntilStep("wait for room join", () => Client.Room != null);
diff --git a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
index d4e591cf09..6851df3832 100644
--- a/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
+++ b/osu.Game.Tests/OnlinePlay/TestSceneCatchUpSyncManager.cs
@@ -31,32 +31,24 @@ namespace osu.Game.Tests.OnlinePlay
}
[Test]
- public void TestMasterClockStartsWhenAllPlayerClocksHaveFrames()
+ public void TestPlayerClocksStartWhenAllHaveFrames()
{
setWaiting(() => player1, false);
- assertMasterState(false);
assertPlayerClockState(() => player1, false);
assertPlayerClockState(() => player2, false);
setWaiting(() => player2, false);
- assertMasterState(true);
assertPlayerClockState(() => player1, true);
assertPlayerClockState(() => player2, true);
}
[Test]
- public void TestMasterClockDoesNotStartWhenNoneReadyForMaximumDelayTime()
- {
- AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
- assertMasterState(false);
- }
-
- [Test]
- public void TestMasterClockStartsWhenAnyReadyForMaximumDelayTime()
+ public void TestReadyPlayersStartWhenReadyForMaximumDelayTime()
{
setWaiting(() => player1, false);
AddWaitStep($"wait {CatchUpSyncManager.MAXIMUM_START_DELAY} milliseconds", (int)Math.Ceiling(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
- assertMasterState(true);
+ assertPlayerClockState(() => player1, true);
+ assertPlayerClockState(() => player2, false);
}
[Test]
@@ -153,9 +145,6 @@ namespace osu.Game.Tests.OnlinePlay
private void setPlayerClockTime(Func playerClock, double offsetFromMaster)
=> AddStep($"set player clock {playerClock().Id} = master - {offsetFromMaster}", () => playerClock().Seek(master.CurrentTime - offsetFromMaster));
- private void assertMasterState(bool running)
- => AddAssert($"master clock {(running ? "is" : "is not")} running", () => master.IsRunning == running);
-
private void assertCatchingUp(Func playerClock, bool catchingUp) =>
AddAssert($"player clock {playerClock().Id} {(catchingUp ? "is" : "is not")} catching up", () => playerClock().IsCatchingUp == catchingUp);
@@ -201,6 +190,11 @@ namespace osu.Game.Tests.OnlinePlay
private class TestManualClock : ManualClock, IAdjustableClock
{
+ public TestManualClock()
+ {
+ IsRunning = true;
+ }
+
public void Start() => IsRunning = true;
public void Stop() => IsRunning = false;
diff --git a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
index 25619de323..28ad7ed6a7 100644
--- a/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
+++ b/osu.Game.Tests/Rulesets/TestSceneRulesetSkinProvidingContainer.cs
@@ -2,14 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
-using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Rulesets;
using osu.Game.Skinning;
@@ -18,14 +19,21 @@ using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Rulesets
{
+ [HeadlessTest]
public class TestSceneRulesetSkinProvidingContainer : OsuTestScene
{
private SkinRequester requester;
protected override Ruleset CreateRuleset() => new TestSceneRulesetDependencies.TestRuleset();
- [Cached(typeof(ISkinSource))]
- private readonly ISkinSource testSource = new TestSkinProvider();
+ [Test]
+ public void TestRulesetResources()
+ {
+ setupProviderStep();
+
+ AddAssert("ruleset texture retrieved via skin", () => requester.GetTexture("test-image") != null);
+ AddAssert("ruleset sample retrieved via skin", () => requester.GetSample(new SampleInfo("test-sample")) != null);
+ }
[Test]
public void TestEarlyAddedSkinRequester()
@@ -38,7 +46,7 @@ namespace osu.Game.Tests.Rulesets
rulesetSkinProvider.Add(requester = new SkinRequester());
- requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture(TestSkinProvider.TEXTURE_NAME);
+ requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("test-image");
Child = rulesetSkinProvider;
});
@@ -46,6 +54,15 @@ namespace osu.Game.Tests.Rulesets
AddAssert("requester got correct initial texture", () => textureOnLoad != null);
}
+ private void setupProviderStep()
+ {
+ AddStep("setup provider", () =>
+ {
+ Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin)
+ .WithChild(requester = new SkinRequester());
+ });
+ }
+
private class SkinRequester : Drawable, ISkin
{
private ISkinSource skin;
@@ -68,28 +85,5 @@ namespace osu.Game.Tests.Rulesets
public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
}
-
- private class TestSkinProvider : ISkinSource
- {
- public const string TEXTURE_NAME = "some-texture";
-
- public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
-
- public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => componentName == TEXTURE_NAME ? Texture.WhitePixel : null;
-
- public ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
-
- public IBindable GetConfig(TLookup lookup) => throw new NotImplementedException();
-
- public event Action SourceChanged
- {
- add { }
- remove { }
- }
-
- public ISkin FindProvider(Func lookupFunction) => lookupFunction(this) ? this : null;
-
- public IEnumerable AllSources => new[] { this };
- }
}
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
new file mode 100644
index 0000000000..d80fbfe309
--- /dev/null
+++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs
@@ -0,0 +1,121 @@
+// 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.Screens;
+using osu.Game.Online.API;
+using osu.Game.Online.Rooms;
+using osu.Game.Online.Solo;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Screens.Ranking;
+
+namespace osu.Game.Tests.Visual.Gameplay
+{
+ public class TestScenePlayerScoreSubmission : OsuPlayerTestScene
+ {
+ protected override bool AllowFail => allowFail;
+
+ private bool allowFail;
+
+ private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
+
+ protected override bool HasCustomSteps => true;
+
+ protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false);
+
+ [Test]
+ public void TestNoSubmissionOnResultsWithNoToken()
+ {
+ prepareTokenResponse(false);
+
+ CreateTest(() => allowFail = false);
+
+ AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
+
+ AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
+ AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
+
+ AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
+
+ AddAssert("ensure no submission", () => Player.SubmittedScore == null);
+ }
+
+ [Test]
+ public void TestSubmissionOnResults()
+ {
+ prepareTokenResponse(true);
+
+ CreateTest(() => allowFail = false);
+
+ AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
+
+ AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
+ AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime()));
+
+ AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen);
+ AddAssert("ensure passing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == true);
+ }
+
+ [Test]
+ public void TestNoSubmissionOnExitWithNoToken()
+ {
+ prepareTokenResponse(false);
+
+ CreateTest(() => allowFail = false);
+
+ AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
+
+ AddStep("exit", () => Player.Exit());
+ AddAssert("ensure no submission", () => Player.SubmittedScore == null);
+ }
+
+ [Test]
+ public void TestSubmissionOnFail()
+ {
+ prepareTokenResponse(true);
+
+ CreateTest(() => allowFail = true);
+
+ AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
+ AddUntilStep("wait for fail", () => Player.HasFailed);
+ AddStep("exit", () => Player.Exit());
+
+ AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
+ }
+
+ [Test]
+ public void TestSubmissionOnExit()
+ {
+ prepareTokenResponse(true);
+
+ CreateTest(() => allowFail = false);
+
+ AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
+ AddStep("exit", () => Player.Exit());
+ AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
+ }
+
+ private void prepareTokenResponse(bool validToken)
+ {
+ AddStep("Prepare test API", () =>
+ {
+ dummyAPI.HandleRequest = request =>
+ {
+ switch (request)
+ {
+ case CreateSoloScoreRequest tokenRequest:
+ if (validToken)
+ tokenRequest.TriggerSuccess(new APIScoreToken { ID = 1234 });
+ else
+ tokenRequest.TriggerFailure(new APIException("something went wrong!", null));
+ return true;
+ }
+
+ return false;
+ };
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
index 6eeb3596a8..7584d67afe 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -41,8 +39,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved]
private OsuGameBase game { get; set; }
- private int nextFrame;
-
private BeatmapSetInfo importedBeatmap;
private int importedBeatmapId;
@@ -51,8 +47,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{
base.SetUpSteps();
- AddStep("reset sent frames", () => nextFrame = 0);
-
AddStep("import beatmap", () =>
{
importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
@@ -105,7 +99,8 @@ namespace osu.Game.Tests.Visual.Gameplay
waitForPlayer();
checkPaused(true);
- sendFrames(1000); // send enough frames to ensure play won't be paused
+ // send enough frames to ensure play won't be paused
+ sendFrames(100);
checkPaused(false);
}
@@ -114,12 +109,12 @@ namespace osu.Game.Tests.Visual.Gameplay
public void TestSpectatingDuringGameplay()
{
start();
+ sendFrames(300);
loadSpectatingScreen();
waitForPlayer();
- AddStep("advance frame count", () => nextFrame = 300);
- sendFrames();
+ sendFrames(300);
AddUntilStep("playing from correct point in time", () => player.ChildrenOfType().First().FrameStableClock.CurrentTime > 30000);
}
@@ -220,11 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void sendFrames(int count = 10)
{
- AddStep("send frames", () =>
- {
- testSpectatorClient.SendFrames(streamingUser.Id, nextFrame, count);
- nextFrame += count;
- });
+ AddStep("send frames", () => testSpectatorClient.SendFrames(streamingUser.Id, count));
}
private void loadSpectatingScreen()
@@ -232,14 +223,5 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("load screen", () => LoadScreen(spectatorScreen = new SoloSpectator(streamingUser)));
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded);
}
-
- internal class TestUserLookupCache : UserLookupCache
- {
- protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User
- {
- Id = lookup,
- Username = $"User {lookup}"
- });
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs
deleted file mode 100644
index c665a57452..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Framework.Allocation;
-using osu.Game.Beatmaps;
-using osu.Game.Online.Rooms;
-using osu.Game.Rulesets;
-using osu.Game.Screens.OnlinePlay;
-using osu.Game.Users;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public abstract class RoomManagerTestScene : RoomTestScene
- {
- [Cached(Type = typeof(IRoomManager))]
- protected TestRoomManager RoomManager { get; } = new TestRoomManager();
-
- public override void SetUpSteps()
- {
- base.SetUpSteps();
-
- AddStep("clear rooms", () => RoomManager.Rooms.Clear());
- }
-
- protected void AddRooms(int count, RulesetInfo ruleset = null)
- {
- AddStep("add rooms", () =>
- {
- for (int i = 0; i < count; i++)
- {
- var room = new Room
- {
- RoomID = { Value = i },
- Name = { Value = $"Room {i}" },
- Host = { Value = new User { Username = "Host" } },
- EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
- Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }
- };
-
- if (ruleset != null)
- {
- room.Playlist.Add(new PlaylistItem
- {
- Ruleset = { Value = ruleset },
- Beatmap =
- {
- Value = new BeatmapInfo
- {
- Metadata = new BeatmapMetadata()
- }
- }
- });
- }
-
- RoomManager.Rooms.Add(room);
- }
- });
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs
deleted file mode 100644
index 1785c99784..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System;
-using osu.Framework.Bindables;
-using osu.Game.Online.Rooms;
-using osu.Game.Screens.OnlinePlay;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public class TestRoomManager : IRoomManager
- {
- public event Action RoomsUpdated
- {
- add { }
- remove { }
- }
-
- public readonly BindableList Rooms = new BindableList();
-
- public IBindable InitialRoomsReceived { get; } = new Bindable(true);
-
- IBindableList IRoomManager.Rooms => Rooms;
-
- public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room);
-
- public void JoinRoom(Room room, Action onSuccess = null, Action onError = null)
- {
- }
-
- public void PartRoom()
- {
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
index 9f24347ae9..471d0b6c98 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
@@ -4,17 +4,21 @@
using System;
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneLoungeRoomInfo : RoomTestScene
+ public class TestSceneLoungeRoomInfo : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
+ SelectedRoom.Value = new Room();
+
Child = new RoomInfo
{
Anchor = Anchor.Centre,
@@ -23,15 +27,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
};
});
- public override void SetUpSteps()
- {
- // Todo: Temp
- }
-
[Test]
public void TestNonSelectedRoom()
{
- AddStep("set null room", () => Room.RoomID.Value = null);
+ AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null);
}
[Test]
@@ -39,11 +38,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set open room", () =>
{
- Room.RoomID.Value = 0;
- Room.Name.Value = "Room 0";
- Room.Host.Value = new User { Username = "peppy", Id = 2 };
- Room.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
- Room.Status.Value = new RoomStatusOpen();
+ SelectedRoom.Value.RoomID.Value = 0;
+ SelectedRoom.Value.Name.Value = "Room 0";
+ SelectedRoom.Value.Host.Value = new User { Username = "peppy", Id = 2 };
+ SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMonths(1);
+ SelectedRoom.Value.Status.Value = new RoomStatusOpen();
});
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
index 5682fd5c3c..75cc687ee8 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs
@@ -3,24 +3,26 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneLoungeRoomsContainer : RoomManagerTestScene
+ public class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
{
+ protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
+
private RoomsContainer container;
- [BackgroundDependencyLoader]
- private void load()
+ [SetUp]
+ public new void Setup() => Schedule(() =>
{
Child = container = new RoomsContainer
{
@@ -29,12 +31,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
Width = 0.5f,
JoinRequested = joinRequested
};
- }
+ });
[Test]
public void TestBasicListChanges()
{
- AddRooms(3);
+ AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("has 3 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault()));
@@ -51,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestKeyboardNavigation()
{
- AddRooms(3);
+ AddStep("add rooms", () => RoomManager.AddRooms(3));
AddAssert("no selection", () => checkRoomSelected(null));
@@ -72,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestClickDeselection()
{
- AddRooms(1);
+ AddStep("add room", () => RoomManager.AddRooms(1));
AddAssert("no selection", () => checkRoomSelected(null));
@@ -91,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestStringFiltering()
{
- AddRooms(4);
+ AddStep("add rooms", () => RoomManager.AddRooms(4));
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
@@ -107,21 +109,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestRulesetFiltering()
{
- AddRooms(2, new OsuRuleset().RulesetInfo);
- AddRooms(3, new CatchRuleset().RulesetInfo);
+ AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo));
+ AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo));
+ // Todo: What even is this case...?
+ AddStep("set empty filter criteria", () => container.Filter(null));
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo }));
-
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo }));
-
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
}
- private bool checkRoomSelected(Room room) => Room == room;
+ private bool checkRoomSelected(Room room) => SelectedRoom.Value == room;
private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
index 9ad9f2c883..d66603a448 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs
@@ -11,11 +11,12 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual.OnlinePlay;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMatchBeatmapDetailArea : RoomTestScene
+ public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene
{
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@@ -26,6 +27,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUp]
public new void Setup() => Schedule(() =>
{
+ SelectedRoom.Value = new Room();
+
Child = new MatchBeatmapDetailArea
{
Anchor = Anchor.Centre,
@@ -37,9 +40,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createNewItem()
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
- ID = Room.Playlist.Count,
+ ID = SelectedRoom.Value.Playlist.Count,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
RequiredMods =
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
index 7cdc6b1a7d..71ba5db481 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs
@@ -7,46 +7,49 @@ using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMatchHeader : RoomTestScene
+ public class TestSceneMatchHeader : OnlinePlayTestScene
{
- public TestSceneMatchHeader()
- {
- Child = new Header();
- }
-
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value = new Room
{
- Beatmap =
+ Name = { Value = "A very awesome room" },
+ Host = { Value = new User { Id = 2, Username = "peppy" } },
+ Playlist =
{
- Value = new BeatmapInfo
+ new PlaylistItem
{
- Metadata = new BeatmapMetadata
+ Beatmap =
{
- Title = "Title",
- Artist = "Artist",
- AuthorString = "Author",
+ Value = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata
+ {
+ Title = "Title",
+ Artist = "Artist",
+ AuthorString = "Author",
+ },
+ Version = "Version",
+ Ruleset = new OsuRuleset().RulesetInfo
+ }
},
- Version = "Version",
- Ruleset = new OsuRuleset().RulesetInfo
+ RequiredMods =
+ {
+ new OsuModDoubleTime(),
+ new OsuModNoFail(),
+ new OsuModRelax(),
+ }
}
- },
- RequiredMods =
- {
- new OsuModDoubleTime(),
- new OsuModNoFail(),
- new OsuModRelax(),
}
- });
+ };
- Room.Name.Value = "A very awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
+ Child = new Header();
});
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
index 64eaf0556b..a7a5f3af39 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs
@@ -2,72 +2,74 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
-using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMatchLeaderboard : RoomTestScene
+ public class TestSceneMatchLeaderboard : OnlinePlayTestScene
{
- protected override bool UseOnlineAPI => true;
-
- public TestSceneMatchLeaderboard()
- {
- Add(new MatchLeaderboard
- {
- Origin = Anchor.Centre,
- Anchor = Anchor.Centre,
- Size = new Vector2(550f, 450f),
- Scope = MatchLeaderboardScope.Overall,
- });
- }
-
[BackgroundDependencyLoader]
- private void load(IAPIProvider api)
+ private void load()
{
- var req = new GetRoomScoresRequest();
- req.Success += v => { };
- req.Failure += _ => { };
+ ((DummyAPIAccess)API).HandleRequest = r =>
+ {
+ switch (r)
+ {
+ case GetRoomLeaderboardRequest leaderboardRequest:
+ leaderboardRequest.TriggerSuccess(new APILeaderboard
+ {
+ Leaderboard = new List
+ {
+ new APIUserScoreAggregate
+ {
+ UserID = 2,
+ User = new User { Id = 2, Username = "peppy" },
+ TotalScore = 995533,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 6,
+ Accuracy = 0.9851
+ },
+ new APIUserScoreAggregate
+ {
+ UserID = 1040328,
+ User = new User { Id = 1040328, Username = "smoogipoo" },
+ TotalScore = 981100,
+ RoomID = 3,
+ CompletedBeatmaps = 1,
+ TotalAttempts = 9,
+ Accuracy = 0.937
+ }
+ }
+ });
+ return true;
+ }
- api.Queue(req);
+ return false;
+ };
}
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.RoomID.Value = 3;
+ SelectedRoom.Value = new Room { RoomID = { Value = 3 } };
+
+ Child = new MatchLeaderboard
+ {
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Size = new Vector2(550f, 450f),
+ Scope = MatchLeaderboardScope.Overall,
+ };
});
-
- private class GetRoomScoresRequest : APIRequest>
- {
- protected override string Target => "rooms/3/leaderboard";
- }
-
- private class RoomScore
- {
- [JsonProperty("user")]
- public User User { get; set; }
-
- [JsonProperty("accuracy")]
- public double Accuracy { get; set; }
-
- [JsonProperty("total_score")]
- public int TotalScore { get; set; }
-
- [JsonProperty("pp")]
- public double PP { get; set; }
-
- [JsonProperty("attempts")]
- public int TotalAttempts { get; set; }
-
- [JsonProperty("completed")]
- public int CompletedAttempts { get; set; }
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
index 5ad35be0ec..e14df62af1 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
@@ -3,74 +3,40 @@
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Timing;
-using osu.Game.Database;
-using osu.Game.Online.Spectator;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play.HUD;
-using osu.Game.Tests.Visual.Spectator;
-using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene
{
- [Cached(typeof(SpectatorClient))]
- private TestSpectatorClient spectatorClient = new TestSpectatorClient();
-
- [Cached(typeof(UserLookupCache))]
- private UserLookupCache lookupCache = new TestUserLookupCache();
-
- protected override Container Content => content;
- private readonly Container content;
-
- private readonly Dictionary clocks = new Dictionary
- {
- { PLAYER_1_ID, new ManualClock() },
- { PLAYER_2_ID, new ManualClock() }
- };
-
- public TestSceneMultiSpectatorLeaderboard()
- {
- base.Content.AddRange(new Drawable[]
- {
- spectatorClient,
- lookupCache,
- content = new Container { RelativeSizeAxes = Axes.Both }
- });
- }
+ private Dictionary clocks;
+ private MultiSpectatorLeaderboard leaderboard;
[SetUpSteps]
public new void SetUpSteps()
{
- MultiSpectatorLeaderboard leaderboard = null;
-
AddStep("reset", () =>
{
Clear();
- foreach (var (userId, clock) in clocks)
+ clocks = new Dictionary
{
- spectatorClient.EndPlay(userId);
- clock.CurrentTime = 0;
- }
+ { PLAYER_1_ID, new ManualClock() },
+ { PLAYER_2_ID, new ManualClock() }
+ };
+
+ foreach (var (userId, _) in clocks)
+ SpectatorClient.StartPlay(userId, 0);
});
AddStep("create leaderboard", () =>
{
- foreach (var (userId, _) in clocks)
- spectatorClient.StartPlay(userId, 0);
-
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
-
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
var scoreProcessor = new OsuScoreProcessor();
scoreProcessor.ApplyBeatmap(playable);
@@ -96,10 +62,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
// For player 2, send frames in sets of 10.
for (int i = 0; i < 100; i++)
{
- spectatorClient.SendFrames(PLAYER_1_ID, i, 1);
+ SpectatorClient.SendFrames(PLAYER_1_ID, 1);
if (i % 10 == 0)
- spectatorClient.SendFrames(PLAYER_2_ID, i, 10);
+ SpectatorClient.SendFrames(PLAYER_2_ID, 10);
}
});
@@ -145,17 +111,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void assertCombo(int userId, int expectedCombo)
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType().Single(s => s.User?.Id == userId).Combo.Value == expectedCombo);
-
- private class TestUserLookupCache : UserLookupCache
- {
- protected override Task ComputeValueAsync(int lookup, CancellationToken token = default)
- {
- return Task.FromResult(new User
- {
- Id = lookup,
- Username = $"User {lookup}"
- });
- }
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index b91391c409..b8db4067fb 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -3,31 +3,20 @@
using System.Collections.Generic;
using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
-using osu.Game.Database;
-using osu.Game.Online.Spectator;
+using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps.IO;
-using osu.Game.Tests.Visual.Spectator;
-using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiSpectatorScreen : MultiplayerTestScene
{
- [Cached(typeof(SpectatorClient))]
- private TestSpectatorClient spectatorClient = new TestSpectatorClient();
-
- [Cached(typeof(UserLookupCache))]
- private UserLookupCache lookupCache = new TestUserLookupCache();
-
[Resolved]
private OsuGameBase game { get; set; }
@@ -37,7 +26,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiSpectatorScreen spectatorScreen;
private readonly List playingUserIds = new List();
- private readonly Dictionary nextFrame = new Dictionary();
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
@@ -51,25 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1;
}
- public override void SetUpSteps()
- {
- base.SetUpSteps();
-
- AddStep("reset sent frames", () => nextFrame.Clear());
-
- AddStep("add streaming client", () =>
- {
- Remove(spectatorClient);
- Add(spectatorClient);
- });
-
- AddStep("finish previous gameplay", () =>
- {
- foreach (var id in playingUserIds)
- spectatorClient.EndPlay(id);
- playingUserIds.Clear();
- });
- }
+ [SetUp]
+ public new void Setup() => Schedule(() => playingUserIds.Clear());
[Test]
public void TestDelayedStart()
@@ -80,18 +51,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID);
playingUserIds.Add(PLAYER_1_ID);
playingUserIds.Add(PLAYER_2_ID);
- nextFrame[PLAYER_1_ID] = 0;
- nextFrame[PLAYER_2_ID] = 0;
});
loadSpectateScreen(false);
AddWaitStep("wait a bit", 10);
- AddStep("load player first_player_id", () => spectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
+ AddStep("load player first_player_id", () => SpectatorClient.StartPlay(PLAYER_1_ID, importedBeatmapId));
AddUntilStep("one player added", () => spectatorScreen.ChildrenOfType().Count() == 1);
AddWaitStep("wait a bit", 10);
- AddStep("load player second_player_id", () => spectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
+ AddStep("load player second_player_id", () => SpectatorClient.StartPlay(PLAYER_2_ID, importedBeatmapId));
AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2);
}
@@ -107,6 +76,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 20);
}
+ [Test]
+ public void TestTimeDoesNotProgressWhileAllPlayersPaused()
+ {
+ start(new[] { PLAYER_1_ID, PLAYER_2_ID });
+ loadSpectateScreen();
+
+ sendFrames(PLAYER_1_ID, 40);
+ sendFrames(PLAYER_2_ID, 20);
+
+ checkPaused(PLAYER_2_ID, true);
+ checkPausedInstant(PLAYER_1_ID, false);
+ AddAssert("master clock still running", () => this.ChildrenOfType().Single().IsRunning);
+
+ checkPaused(PLAYER_1_ID, true);
+ AddUntilStep("master clock paused", () => !this.ChildrenOfType().Single().IsRunning);
+ }
+
[Test]
public void TestPlayersMustStartSimultaneously()
{
@@ -182,7 +168,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
// Send initial frames for both players. A few more for player 1.
sendFrames(PLAYER_1_ID, 1000);
- sendFrames(PLAYER_2_ID, 10);
+ sendFrames(PLAYER_2_ID, 30);
checkPausedInstant(PLAYER_1_ID, false);
checkPausedInstant(PLAYER_2_ID, false);
@@ -210,8 +196,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
sendFrames(PLAYER_1_ID, 10);
sendFrames(PLAYER_2_ID, 20);
- assertMuted(PLAYER_1_ID, false);
- assertMuted(PLAYER_2_ID, true);
+ checkPaused(PLAYER_1_ID, false);
+ assertOneNotMuted();
checkPaused(PLAYER_1_ID, true);
assertMuted(PLAYER_1_ID, true);
@@ -229,6 +215,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertMuted(PLAYER_2_ID, true);
}
+ [Test]
+ public void TestSpectatingDuringGameplay()
+ {
+ var players = new[] { PLAYER_1_ID, PLAYER_2_ID };
+
+ start(players);
+ sendFrames(players, 300);
+
+ loadSpectateScreen();
+ sendFrames(players, 300);
+
+ AddUntilStep("playing from correct point in time", () => this.ChildrenOfType().All(r => r.FrameStableClock.CurrentTime > 30000));
+ }
+
+ [Test]
+ public void TestSpectatingDuringGameplayWithLateFrames()
+ {
+ start(new[] { PLAYER_1_ID, PLAYER_2_ID });
+ sendFrames(new[] { PLAYER_1_ID, PLAYER_2_ID }, 300);
+
+ loadSpectateScreen();
+ sendFrames(PLAYER_1_ID, 300);
+
+ AddWaitStep("wait maximum start delay seconds", (int)(CatchUpSyncManager.MAXIMUM_START_DELAY / TimePerAction));
+ checkPaused(PLAYER_1_ID, false);
+
+ sendFrames(PLAYER_2_ID, 300);
+ AddUntilStep("player 2 playing from correct point in time", () => getPlayer(PLAYER_2_ID).ChildrenOfType().Single().FrameStableClock.CurrentTime > 30000);
+ }
+
private void loadSpectateScreen(bool waitForPlayerLoad = true)
{
AddStep("load screen", () =>
@@ -242,8 +258,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
}
- private void start(int userId, int? beatmapId = null) => start(new[] { userId }, beatmapId);
-
private void start(int[] userIds, int? beatmapId = null)
{
AddStep("start play", () =>
@@ -251,23 +265,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
foreach (int id in userIds)
{
Client.CurrentMatchPlayingUserIds.Add(id);
- spectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
+ SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
playingUserIds.Add(id);
- nextFrame[id] = 0;
}
});
}
- private void finish(int userId)
- {
- AddStep("end play", () =>
- {
- spectatorClient.EndPlay(userId);
- playingUserIds.Remove(userId);
- nextFrame.Remove(userId);
- });
- }
-
private void sendFrames(int userId, int count = 10) => sendFrames(new[] { userId }, count);
private void sendFrames(int[] userIds, int count = 10)
@@ -275,10 +278,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("send frames", () =>
{
foreach (int id in userIds)
- {
- spectatorClient.SendFrames(id, nextFrame[id], count);
- nextFrame[id] += count;
- }
+ SpectatorClient.SendFrames(id, count);
});
}
@@ -286,7 +286,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
=> AddUntilStep($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state);
private void checkPausedInstant(int userId, bool state)
- => AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state);
+ {
+ checkPaused(userId, state);
+
+ // Todo: The following should work, but is broken because SpectatorScreen retrieves the WorkingBeatmap via the BeatmapManager, bypassing the test scene clock and running real-time.
+ // AddAssert($"{userId} is {(state ? "paused" : "playing")}", () => getPlayer(userId).ChildrenOfType().First().GameplayClock.IsRunning != state);
+ }
+
+ private void assertOneNotMuted() => AddAssert("one player not muted", () => spectatorScreen.ChildrenOfType().Count(p => !p.Mute) == 1);
private void assertMuted(int userId, bool muted)
=> AddAssert($"{userId} {(muted ? "is" : "is not")} muted", () => getInstance(userId).Mute == muted);
@@ -297,17 +304,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
private Player getPlayer(int userId) => getInstance(userId).ChildrenOfType().Single();
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType().Single(p => p.UserId == userId);
-
- internal class TestUserLookupCache : UserLookupCache
- {
- protected override Task ComputeValueAsync(int lookup, CancellationToken token = default)
- {
- return Task.FromResult(new User
- {
- Id = lookup,
- Username = $"User {lookup}"
- });
- }
- }
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 599dfb082b..c93640e7b5 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -18,6 +18,7 @@ using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
@@ -30,14 +31,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayer : ScreenTestScene
{
- private TestMultiplayer multiplayerScreen;
-
private BeatmapManager beatmaps;
private RulesetStore rulesets;
private BeatmapSetInfo importedSet;
- private TestMultiplayerClient client => multiplayerScreen.Client;
- private Room room => client.APIRoom;
+ private DependenciesScreen dependenciesScreen;
+ private TestMultiplayer multiplayerScreen;
+ private TestMultiplayerClient client;
public TestSceneMultiplayer()
{
@@ -229,30 +229,43 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void loadMultiplayer()
{
- AddStep("show", () =>
+ AddStep("create multiplayer screen", () => multiplayerScreen = new TestMultiplayer());
+
+ AddStep("load dependencies", () =>
{
- multiplayerScreen = new TestMultiplayer();
+ client = new TestMultiplayerClient(multiplayerScreen.RoomManager);
- // Needs to be added at a higher level since the multiplayer screen becomes non-current.
- Child = multiplayerScreen.Client;
+ // The screen gets suspended so it stops receiving updates.
+ Child = client;
- LoadScreen(multiplayerScreen);
+ LoadScreen(dependenciesScreen = new DependenciesScreen(client));
});
- AddUntilStep("wait for loaded", () => multiplayerScreen.IsLoaded);
+ AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded);
+
+ AddStep("load multiplayer", () => LoadScreen(multiplayerScreen));
+ AddUntilStep("wait for multiplayer to load", () => multiplayerScreen.IsLoaded);
}
- private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
+ ///
+ /// Used for the sole purpose of adding as a resolvable dependency.
+ ///
+ private class DependenciesScreen : OsuScreen
{
[Cached(typeof(MultiplayerClient))]
public readonly TestMultiplayerClient Client;
- public TestMultiplayer()
+ public DependenciesScreen(TestMultiplayerClient client)
{
- Client = new TestMultiplayerClient((TestMultiplayerRoomManager)RoomManager);
+ Client = client;
}
+ }
- protected override RoomManager CreateRoomManager() => new TestMultiplayerRoomManager();
+ private class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
+ {
+ public new TestMultiplayerRoomManager RoomManager { get; private set; }
+
+ protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager();
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index af2f6fa5fe..0e368b59dd 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -6,12 +6,11 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
-using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Spectator;
using osu.Game.Replays.Legacy;
@@ -19,37 +18,20 @@ using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
-using osu.Game.Tests.Visual.Online;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Tests.Visual.Spectator;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerTestScene
{
- private const int users = 16;
+ private static IEnumerable users => Enumerable.Range(0, 16);
- [Cached(typeof(SpectatorClient))]
- private TestMultiplayerSpectatorClient spectatorClient = new TestMultiplayerSpectatorClient();
-
- [Cached(typeof(UserLookupCache))]
- private UserLookupCache lookupCache = new TestSceneCurrentlyPlayingDisplay.TestUserLookupCache();
+ public new TestMultiplayerSpectatorClient SpectatorClient => (TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient;
private MultiplayerGameplayLeaderboard leaderboard;
-
- protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
-
private OsuConfigManager config;
- public TestSceneMultiplayerGameplayLeaderboard()
- {
- base.Content.Children = new Drawable[]
- {
- spectatorClient,
- lookupCache,
- Content
- };
- }
-
[BackgroundDependencyLoader]
private void load()
{
@@ -59,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUpSteps]
public override void SetUpSteps()
{
- AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = lookupCache.GetUserAsync(1).Result);
+ AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
AddStep("create leaderboard", () =>
{
@@ -70,14 +52,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
- for (int i = 0; i < users; i++)
- spectatorClient.StartPlay(i, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
+ foreach (var user in users)
+ SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
- spectatorClient.Schedule(() =>
- {
- Client.CurrentMatchPlayingUserIds.Clear();
- Client.CurrentMatchPlayingUserIds.AddRange(spectatorClient.PlayingUsers);
- });
+ // Todo: This is REALLY bad.
+ Client.CurrentMatchPlayingUserIds.AddRange(users);
Children = new Drawable[]
{
@@ -86,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
scoreProcessor.ApplyBeatmap(playable);
- LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, spectatorClient.PlayingUsers.ToArray())
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -100,24 +79,32 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestScoreUpdates()
{
- AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 100);
+ AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100);
AddToggleStep("switch compact mode", expanded => leaderboard.Expanded.Value = expanded);
}
[Test]
public void TestUserQuit()
{
- AddRepeatStep("mark user quit", () => Client.CurrentMatchPlayingUserIds.RemoveAt(0), users);
+ foreach (var user in users)
+ AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull()));
}
[Test]
public void TestChangeScoringMode()
{
- AddRepeatStep("update state", () => spectatorClient.RandomlyUpdateState(), 5);
+ AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 5);
AddStep("change to classic", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Classic));
AddStep("change to standardised", () => config.SetValue(OsuSetting.ScoreDisplayMode, ScoringMode.Standardised));
}
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
+ protected class TestDependencies : MultiplayerTestSceneDependencies
+ {
+ protected override TestSpectatorClient CreateSpectatorClient() => new TestMultiplayerSpectatorClient();
+ }
+
public class TestMultiplayerSpectatorClient : TestSpectatorClient
{
private readonly Dictionary lastHeaders = new Dictionary();
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
index 6b03b53b4b..4e08ffef17 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs
@@ -1,7 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using osu.Framework.Allocation;
+using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
@@ -10,18 +10,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene
{
- [Cached]
- private readonly OnlinePlayBeatmapAvailabilityTracker availablilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
-
- [BackgroundDependencyLoader]
- private void load()
+ [SetUp]
+ public new void Setup() => Schedule(() =>
{
+ SelectedRoom.Value = new Room();
+
Child = new MultiplayerMatchFooter
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Height = 50
};
- }
+ });
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
index 5b059c06f5..8bcb9cebbc 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs
@@ -29,7 +29,7 @@ using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestSceneMultiplayerMatchSongSelect : RoomTestScene
+ public class TestSceneMultiplayerMatchSongSelect : MultiplayerTestScene
{
private BeatmapManager manager;
private RulesetStore rulesets;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index e8ebc0c426..955be6ca21 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -49,13 +49,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.Name.Value = "Test Room";
+ SelectedRoom.Value = new Room { Name = { Value = "Test Room" } };
});
[SetUpSteps]
public void SetupSteps()
{
- AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(Room)));
+ AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value)));
AddUntilStep("wait for load", () => screen.IsCurrentScreen());
}
@@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set playlist", () =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
@@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
@@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
index 7f8f04b718..6526f7eea7 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
@@ -22,8 +22,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMultiplayerParticipantsList : MultiplayerTestScene
{
- [SetUp]
- public new void Setup() => Schedule(createNewParticipantsList);
+ [SetUpSteps]
+ public void SetupSteps()
+ {
+ createNewParticipantsList();
+ }
[Test]
public void TestAddUser()
@@ -88,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestCorrectInitialState()
{
AddStep("set to downloading map", () => Client.ChangeBeatmapAvailability(BeatmapAvailability.Downloading(0)));
- AddStep("recreate list", createNewParticipantsList);
+ createNewParticipantsList();
checkProgressBarVisibility(true);
}
@@ -233,7 +236,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createNewParticipantsList()
{
- Child = new ParticipantsList { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Size = new Vector2(380, 0.7f) };
+ ParticipantsList participantsList = null;
+
+ AddStep("create new list", () => Child = participantsList = new ParticipantsList
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Y,
+ Size = new Vector2(380, 0.7f)
+ });
+
+ AddUntilStep("wait for list to load", () => participantsList.IsLoaded);
}
private void checkProgressBarVisibility(bool visible) =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
index 929cd6ca80..0e036e8868 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerReadyButton.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -27,7 +28,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public class TestSceneMultiplayerReadyButton : MultiplayerTestScene
{
private MultiplayerReadyButton button;
- private OnlinePlayBeatmapAvailabilityTracker beatmapTracker;
private BeatmapSetInfo importedSet;
private readonly Bindable selectedItem = new Bindable();
@@ -43,18 +43,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
-
- Add(beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker
- {
- SelectedItem = { BindTarget = selectedItem }
- });
-
- Dependencies.Cache(beatmapTracker);
}
[SetUp]
public new void Setup() => Schedule(() =>
{
+ AvailabilityTracker.SelectedItem.BindTo(selectedItem);
+
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem
@@ -71,18 +66,22 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
- OnReadyClick = async () =>
+ OnReadyClick = () =>
{
readyClickOperation = OngoingOperationTracker.BeginOperation();
- if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
+ Task.Run(async () =>
{
- await Client.StartMatch();
- return;
- }
+ if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
+ {
+ await Client.StartMatch();
+ return;
+ }
- await Client.ToggleReady();
- readyClickOperation.Dispose();
+ await Client.ToggleReady();
+
+ readyClickOperation.Dispose();
+ });
}
});
});
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
index c008771fd9..b17427a30b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerRoomManager.cs
@@ -3,37 +3,38 @@
using System;
using NUnit.Framework;
-using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
[HeadlessTest]
- public class TestSceneMultiplayerRoomManager : RoomTestScene
+ public class TestSceneMultiplayerRoomManager : MultiplayerTestScene
{
- private TestMultiplayerRoomContainer roomContainer;
- private TestMultiplayerRoomManager roomManager => roomContainer.RoomManager;
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
+ public TestSceneMultiplayerRoomManager()
+ : base(false)
+ {
+ }
[Test]
public void TestPollsInitially()
{
AddStep("create room manager with a few rooms", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
- roomManager.PartRoom();
- roomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
- roomManager.PartRoom();
- roomManager.ClearRooms();
- });
+ RoomManager.CreateRoom(createRoom(r => r.Name.Value = "1"));
+ RoomManager.PartRoom();
+ RoomManager.CreateRoom(createRoom(r => r.Name.Value = "2"));
+ RoomManager.PartRoom();
+ RoomManager.ClearRooms();
});
- AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
- AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
+ AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
+ AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -41,19 +42,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a few rooms", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
});
- AddStep("disconnect", () => roomContainer.Client.Disconnect());
+ AddStep("disconnect", () => Client.Disconnect());
- AddAssert("rooms cleared", () => ((RoomManager)roomManager).Rooms.Count == 0);
- AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
+ AddAssert("rooms cleared", () => ((RoomManager)RoomManager).Rooms.Count == 0);
+ AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -61,20 +59,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a few rooms", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
});
- AddStep("disconnect", () => roomContainer.Client.Disconnect());
- AddStep("connect", () => roomContainer.Client.Connect());
+ AddStep("disconnect", () => Client.Disconnect());
+ AddStep("connect", () => Client.Connect());
- AddAssert("manager polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 2);
- AddAssert("initial rooms received", () => roomManager.InitialRoomsReceived.Value);
+ AddAssert("manager polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 2);
+ AddAssert("initial rooms received", () => RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -82,15 +77,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.ClearRooms();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.ClearRooms();
});
- AddAssert("manager not polled for rooms", () => ((RoomManager)roomManager).Rooms.Count == 0);
- AddAssert("initial rooms not received", () => !roomManager.InitialRoomsReceived.Value);
+ AddAssert("manager not polled for rooms", () => ((RoomManager)RoomManager).Rooms.Count == 0);
+ AddAssert("initial rooms not received", () => !RoomManager.InitialRoomsReceived.Value);
}
[Test]
@@ -98,13 +90,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- });
+ RoomManager.CreateRoom(createRoom());
});
- AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
+ AddUntilStep("multiplayer room joined", () => Client.Room != null);
}
[Test]
@@ -112,14 +101,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- roomManager.CreateRoom(createRoom());
- roomManager.PartRoom();
- });
+ RoomManager.CreateRoom(createRoom());
+ RoomManager.PartRoom();
});
- AddAssert("multiplayer room parted", () => roomContainer.Client.Room == null);
+ AddAssert("multiplayer room parted", () => Client.Room == null);
}
[Test]
@@ -127,16 +113,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room manager with a room", () =>
{
- createRoomManager().With(d => d.OnLoadComplete += _ =>
- {
- var r = createRoom();
- roomManager.CreateRoom(r);
- roomManager.PartRoom();
- roomManager.JoinRoom(r);
- });
+ var r = createRoom();
+ RoomManager.CreateRoom(r);
+ RoomManager.PartRoom();
+ RoomManager.JoinRoom(r);
});
- AddUntilStep("multiplayer room joined", () => roomContainer.Client.Room != null);
+ AddUntilStep("multiplayer room joined", () => Client.Room != null);
}
private Room createRoom(Action initFunc = null)
@@ -161,18 +144,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
return room;
}
- private TestMultiplayerRoomManager createRoomManager()
+ private class TestDependencies : MultiplayerTestSceneDependencies
{
- Child = roomContainer = new TestMultiplayerRoomContainer
+ public TestDependencies()
{
- RoomManager =
- {
- TimeBetweenListingPolls = { Value = 1 },
- TimeBetweenSelectionPolls = { Value = 1 }
- }
- };
-
- return roomManager;
+ // Need to set these values as early as possible.
+ RoomManager.TimeBetweenListingPolls.Value = 1;
+ RoomManager.TimeBetweenSelectionPolls.Value = 1;
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
index d00404102c..4966dfbe50 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
@@ -37,40 +38,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
private IDisposable readyClickOperation;
- protected override Container Content => content;
- private readonly Container content;
-
- public TestSceneMultiplayerSpectateButton()
- {
- base.Content.Add(content = new Container
- {
- RelativeSizeAxes = Axes.Both
- });
- }
-
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
-
- return dependencies;
- }
-
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
-
- var beatmapTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } };
- base.Content.Add(beatmapTracker);
- Dependencies.Cache(beatmapTracker);
-
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait();
}
[SetUp]
public new void Setup() => Schedule(() =>
{
+ AvailabilityTracker.SelectedItem.BindTo(selectedItem);
+
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem
@@ -90,11 +70,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
- OnSpectateClick = async () =>
+ OnSpectateClick = () =>
{
readyClickOperation = OngoingOperationTracker.BeginOperation();
- await Client.ToggleSpectate();
- readyClickOperation.Dispose();
+
+ Task.Run(async () =>
+ {
+ await Client.ToggleSpectate();
+ readyClickOperation.Dispose();
+ });
}
},
readyButton = new MultiplayerReadyButton
@@ -102,18 +86,22 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
- OnReadyClick = async () =>
+ OnReadyClick = () =>
{
readyClickOperation = OngoingOperationTracker.BeginOperation();
- if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
+ Task.Run(async () =>
{
- await Client.StartMatch();
- return;
- }
+ if (Client.IsHost && Client.LocalUser?.State == MultiplayerUserState.Ready)
+ {
+ await Client.StartMatch();
+ return;
+ }
- await Client.ToggleReady();
- readyClickOperation.Dispose();
+ await Client.ToggleReady();
+
+ readyClickOperation.Dispose();
+ });
}
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
index d95a95ebe5..e4bf9b36ed 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs
@@ -14,16 +14,18 @@ using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
+using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Playlists;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public class TestScenePlaylistsSongSelect : RoomTestScene
+ public class TestScenePlaylistsSongSelect : OnlinePlayTestScene
{
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@@ -85,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("reset", () =>
{
+ SelectedRoom.Value = new Room();
Ruleset.Value = new OsuRuleset().RulesetInfo;
Beatmap.SetDefault();
SelectedMods.Value = Array.Empty();
@@ -98,14 +101,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestItemAddedIfEmptyOnStart()
{
AddStep("finalise selection", () => songSelect.FinaliseSelection());
- AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
[Test]
public void TestItemAddedWhenCreateNewItemClicked()
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
[Test]
@@ -113,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("finalise selection", () => songSelect.FinaliseSelection());
- AddAssert("playlist has 1 item", () => Room.Playlist.Count == 1);
+ AddAssert("playlist has 1 item", () => SelectedRoom.Value.Playlist.Count == 1);
}
[Test]
@@ -121,7 +124,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("playlist has 2 items", () => Room.Playlist.Count == 2);
+ AddAssert("playlist has 2 items", () => SelectedRoom.Value.Playlist.Count == 2);
}
[Test]
@@ -131,13 +134,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("rearrange", () =>
{
- var item = Room.Playlist[0];
- Room.Playlist.RemoveAt(0);
- Room.Playlist.Add(item);
+ var item = SelectedRoom.Value.Playlist[0];
+ SelectedRoom.Value.Playlist.RemoveAt(0);
+ SelectedRoom.Value.Playlist.Add(item);
});
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("new item has id 2", () => Room.Playlist.Last().ID == 2);
+ AddAssert("new item has id 2", () => SelectedRoom.Value.Playlist.Last().ID == 2);
}
///
@@ -151,8 +154,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2);
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
- AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
- AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)Room.Playlist.Last().RequiredMods[0]).SpeedChange.Value));
+ AddAssert("item 1 has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value));
+ AddAssert("item 2 has rate 2", () => Precision.AlmostEquals(2, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.Last().RequiredMods[0]).SpeedChange.Value));
}
///
@@ -174,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem());
AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
- AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)Room.Playlist.First().RequiredMods[0]).SpeedChange.Value));
+ AddAssert("item has rate 1.5", () => Precision.AlmostEquals(1.5, ((OsuModDoubleTime)SelectedRoom.Value.Playlist.First().RequiredMods[0]).SpeedChange.Value));
}
private class TestPlaylistsSongSelect : PlaylistsSongSelect
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
new file mode 100644
index 0000000000..cdeafdc9a3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Game.Beatmaps;
+using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene
+ {
+ [SetUp]
+ public new void Setup() => Schedule(() =>
+ {
+ SelectedRoom.Value = new Room();
+
+ Child = new StarRatingRangeDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ };
+ });
+
+ [Test]
+ public void TestRange([Values(0, 2, 3, 4, 6, 7)] double min, [Values(0, 2, 3, 4, 6, 7)] double max)
+ {
+ AddStep("set playlist", () =>
+ {
+ SelectedRoom.Value.Playlist.AddRange(new[]
+ {
+ new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = min } } },
+ new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarDifficulty = max } } },
+ });
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs
index 3cedaf9d45..92152bce18 100644
--- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs
@@ -11,6 +11,7 @@ using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
+using osu.Game.Tests.Beatmaps.IO;
using osuTK.Input;
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
@@ -57,8 +58,11 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestPerformAtSongSelectFromPlayerLoader()
{
+ AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
PushAndConfirm(() => new TestPlaySongSelect());
- PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
+
+ AddStep("Press enter", () => InputManager.Key(Key.Enter));
+ AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
@@ -68,8 +72,11 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestPerformAtMenuFromPlayerLoader()
{
+ AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
PushAndConfirm(() => new TestPlaySongSelect());
- PushAndConfirm(() => new PlayerLoader(() => new SoloPlayer()));
+
+ AddStep("Press enter", () => InputManager.Key(Key.Enter));
+ AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu);
diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
index b6dce2c398..af2e4fc91a 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Linq;
using Markdig.Syntax.Inlines;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -9,6 +10,9 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
using osu.Game.Graphics.Containers.Markdown;
using osu.Game.Overlays;
using osu.Game.Overlays.Wiki.Markdown;
@@ -102,7 +106,7 @@ needs_cleanup: true
{
AddStep("Add absolute image", () =>
{
- markdownContainer.DocumentUrl = "https://dev.ppy.sh";
+ markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
});
}
@@ -112,8 +116,7 @@ needs_cleanup: true
{
AddStep("Add relative image", () =>
{
- markdownContainer.DocumentUrl = "https://dev.ppy.sh";
- markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/";
+ markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
});
}
@@ -123,8 +126,7 @@ needs_cleanup: true
{
AddStep("Add paragraph with block image", () =>
{
- markdownContainer.DocumentUrl = "https://dev.ppy.sh";
- markdownContainer.CurrentPath = $"{API.WebsiteRootUrl}/wiki/Interface/";
+ markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
markdownContainer.Text = @"Line before image
![play menu](img/play-menu.jpg ""Main Menu in osu!"")
@@ -138,7 +140,7 @@ Line after image";
{
AddStep("Add inline image", () =>
{
- markdownContainer.DocumentUrl = "https://dev.ppy.sh";
+ markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = "![osu! mode icon](/wiki/shared/mode/osu.png) osu!";
});
}
@@ -148,7 +150,7 @@ Line after image";
{
AddStep("Add Table", () =>
{
- markdownContainer.DocumentUrl = "https://dev.ppy.sh";
+ markdownContainer.CurrentPath = "https://dev.ppy.sh";
markdownContainer.Text = @"
| Image | Name | Effect |
| :-: | :-: | :-- |
@@ -162,15 +164,33 @@ Line after image";
});
}
+ [Test]
+ public void TestWideImageNotExceedContainer()
+ {
+ AddStep("Add image", () =>
+ {
+ markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/";
+ markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")";
+ });
+
+ AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType().First().DelayedLoadCompleted);
+
+ AddStep("Change container width", () =>
+ {
+ markdownContainer.Width = 0.5f;
+ });
+
+ AddAssert("Image not exceed container width", () =>
+ {
+ var spriteImage = markdownContainer.ChildrenOfType().First();
+ return Precision.DefinitelyBigger(markdownContainer.DrawWidth, spriteImage.DrawWidth);
+ });
+ }
+
private class TestMarkdownContainer : WikiMarkdownContainer
{
public LinkInline Link;
- public new string DocumentUrl
- {
- set => base.DocumentUrl = value;
- }
-
public override MarkdownTextFlowContainer CreateTextFlow() => new TestMarkdownTextFlowContainer
{
UrlAdded = link => Link = link,
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
index 618447eae2..b16b61c5c7 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
@@ -3,25 +3,21 @@
using System.Linq;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Playlists;
-using osu.Game.Tests.Visual.Multiplayer;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsLoungeSubScreen : RoomManagerTestScene
+ public class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
{
- private LoungeSubScreen loungeScreen;
+ protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
- [BackgroundDependencyLoader]
- private void load()
- {
- }
+ private LoungeSubScreen loungeScreen;
public override void SetUpSteps()
{
@@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Playlists
[Test]
public void TestScrollSelectedIntoView()
{
- AddRooms(30);
+ AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First()));
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
index 44a79b6598..a320cb240f 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs
@@ -3,7 +3,6 @@
using System;
using NUnit.Framework;
-using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -12,26 +11,28 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
+using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsMatchSettingsOverlay : RoomTestScene
+ public class TestScenePlaylistsMatchSettingsOverlay : OnlinePlayTestScene
{
- [Cached(Type = typeof(IRoomManager))]
- private TestRoomManager roomManager = new TestRoomManager();
+ protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private TestRoomSettings settings;
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
[SetUp]
public new void Setup() => Schedule(() =>
{
- settings = new TestRoomSettings
+ SelectedRoom.Value = new Room();
+
+ Child = settings = new TestRoomSettings
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible }
};
-
- Child = settings;
});
[Test]
@@ -39,19 +40,19 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("clear name and beatmap", () =>
{
- Room.Name.Value = "";
- Room.Playlist.Clear();
+ SelectedRoom.Value.Name.Value = "";
+ SelectedRoom.Value.Playlist.Clear();
});
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set name", () => Room.Name.Value = "Room name");
+ AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
- AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }));
+ AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }));
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
- AddStep("clear name", () => Room.Name.Value = "");
+ AddStep("clear name", () => SelectedRoom.Value.Name.Value = "");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
}
@@ -67,9 +68,9 @@ namespace osu.Game.Tests.Visual.Playlists
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
- Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
- roomManager.CreateRequested = r =>
+ RoomManager.CreateRequested = r =>
{
createdRoom = r;
return true;
@@ -88,11 +89,11 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("setup", () =>
{
- Room.Name.Value = "Test Room";
- Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
+ SelectedRoom.Value.Name.Value = "Test Room";
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
fail = true;
- roomManager.CreateRequested = _ => !fail;
+ RoomManager.CreateRequested = _ => !fail;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
@@ -119,7 +120,12 @@ namespace osu.Game.Tests.Visual.Playlists
public OsuSpriteText ErrorText => ((MatchSettings)Settings).ErrorText;
}
- private class TestRoomManager : IRoomManager
+ private class TestDependencies : OnlinePlayTestSceneDependencies
+ {
+ protected override IRoomManager CreateRoomManager() => new TestRoomManager();
+ }
+
+ protected class TestRoomManager : IRoomManager
{
public const string FAILED_TEXT = "failed";
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
index 255f147ec9..76a78c0a3c 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs
@@ -3,21 +3,23 @@
using NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsParticipantsList : RoomTestScene
+ public class TestScenePlaylistsParticipantsList : OnlinePlayTestScene
{
[SetUp]
public new void Setup() => Schedule(() =>
{
- Room.RoomID.Value = 7;
+ SelectedRoom.Value = new Room { RoomID = { Value = 7 } };
for (int i = 0; i < 50; i++)
{
- Room.RecentParticipants.Add(new User
+ SelectedRoom.Value.RecentParticipants.Add(new User
{
Username = "peppy",
Statistics = new UserStatistics { GlobalRank = 1234 },
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
index 6d7a254ab9..f2bfb80beb 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs
@@ -15,20 +15,17 @@ using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
-using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Playlists
{
- public class TestScenePlaylistsRoomSubScreen : RoomTestScene
+ public class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
{
- [Cached(typeof(IRoomManager))]
- private readonly TestRoomManager roomManager = new TestRoomManager();
-
private BeatmapManager manager;
private RulesetStore rulesets;
@@ -56,8 +53,9 @@ namespace osu.Game.Tests.Visual.Playlists
[SetUpSteps]
public void SetupSteps()
{
+ AddStep("set room", () => SelectedRoom.Value = new Room());
AddStep("ensure has beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait());
- AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(Room)));
+ AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value)));
AddUntilStep("wait for load", () => match.IsCurrentScreen());
}
@@ -66,12 +64,12 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("set room properties", () =>
{
- Room.RoomID.Value = 1;
- Room.Name.Value = "my awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
- Room.RecentParticipants.Add(Room.Host.Value);
- Room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.RoomID.Value = 1;
+ SelectedRoom.Value.Name.Value = "my awesome room";
+ SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.RecentParticipants.Add(SelectedRoom.Value.Host.Value);
+ SelectedRoom.Value.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
@@ -87,9 +85,9 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("set room properties", () =>
{
- Room.Name.Value = "my awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Name.Value = "my awesome room";
+ SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
@@ -103,7 +101,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("click", () => InputManager.Click(MouseButton.Left));
- AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]);
+ AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]);
}
[Test]
@@ -138,9 +136,9 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("load room", () =>
{
- Room.Name.Value = "my awesome room";
- Room.Host.Value = new User { Id = 2, Username = "peppy" };
- Room.Playlist.Add(new PlaylistItem
+ SelectedRoom.Value.Name.Value = "my awesome room";
+ SelectedRoom.Value.Host.Value = new User { Id = 2, Username = "peppy" };
+ SelectedRoom.Value.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = importedSet.Beatmaps[0] },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
@@ -171,30 +169,5 @@ namespace osu.Game.Tests.Visual.Playlists
{
}
}
-
- private class TestRoomManager : IRoomManager
- {
- public event Action RoomsUpdated
- {
- add => throw new NotImplementedException();
- remove => throw new NotImplementedException();
- }
-
- public IBindable InitialRoomsReceived { get; } = new Bindable(true);
-
- public IBindableList Rooms { get; } = new BindableList();
-
- public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
- {
- room.RoomID.Value = 1;
- onSuccess?.Invoke(room);
- }
-
- public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room);
-
- public void PartRoom()
- {
- }
- }
}
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs
new file mode 100644
index 0000000000..fa9179443d
--- /dev/null
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneColourPicker.cs
@@ -0,0 +1,91 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Graphics.UserInterfaceV2;
+using osu.Game.Overlays;
+
+namespace osu.Game.Tests.Visual.UserInterface
+{
+ public class TestSceneColourPicker : OsuTestScene
+ {
+ private readonly Bindable colour = new Bindable(Colour4.Aquamarine);
+
+ [SetUpSteps]
+ public void SetUpSteps()
+ {
+ AddStep("create pickers", () => Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ColumnDimensions = new[]
+ {
+ new Dimension(),
+ new Dimension()
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = @"No OverlayColourProvider",
+ Font = OsuFont.Default.With(size: 40)
+ },
+ new OsuColourPicker
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = colour },
+ }
+ }
+ },
+ new ColourProvidingContainer(OverlayColourScheme.Blue)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = @"With blue OverlayColourProvider",
+ Font = OsuFont.Default.With(size: 40)
+ },
+ new OsuColourPicker
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Current = { BindTarget = colour },
+ }
+ }
+ }
+ }
+ }
+ });
+
+ AddStep("set green", () => colour.Value = Colour4.LimeGreen);
+ AddStep("set white", () => colour.Value = Colour4.White);
+ AddStep("set red", () => colour.Value = Colour4.Red);
+ }
+
+ private class ColourProvidingContainer : Container
+ {
+ [Cached]
+ private OverlayColourProvider provider { get; }
+
+ public ColourProvidingContainer(OverlayColourScheme colourScheme)
+ {
+ provider = new OverlayColourProvider(colourScheme);
+ }
+ }
+ }
+}
diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj
index 35d3c7f202..161e248d96 100644
--- a/osu.Game.Tests/osu.Game.Tests.csproj
+++ b/osu.Game.Tests/osu.Game.Tests.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
index 2084be765a..ba096abd36 100644
--- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
+++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj
@@ -7,7 +7,7 @@
-
+
WinExe
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index 15967c37c2..a44c28eaa6 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -32,10 +32,10 @@ namespace osu.Game.Graphics
return Pink;
case DifficultyRating.Expert:
- return useLighterColour ? PurpleLight : Purple;
+ return PurpleLight;
case DifficultyRating.ExpertPlus:
- return useLighterColour ? Gray9 : Gray0;
+ return useLighterColour ? Gray9 : Color4Extensions.FromHex("#121415");
}
}
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs
new file mode 100644
index 0000000000..5394e5d0aa
--- /dev/null
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuColourPicker.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics.UserInterface;
+
+namespace osu.Game.Graphics.UserInterfaceV2
+{
+ public class OsuColourPicker : ColourPicker
+ {
+ public OsuColourPicker()
+ {
+ CornerRadius = 10;
+ Masking = true;
+ }
+
+ protected override HSVColourPicker CreateHSVColourPicker() => new OsuHSVColourPicker();
+ protected override HexColourPicker CreateHexColourPicker() => new OsuHexColourPicker();
+ }
+}
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs
new file mode 100644
index 0000000000..06056f239b
--- /dev/null
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuHSVColourPicker.cs
@@ -0,0 +1,129 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Overlays;
+using osuTK;
+
+namespace osu.Game.Graphics.UserInterfaceV2
+{
+ public class OsuHSVColourPicker : HSVColourPicker
+ {
+ private const float spacing = 10;
+ private const float corner_radius = 10;
+ private const float control_border_thickness = 3;
+
+ protected override HueSelector CreateHueSelector() => new OsuHueSelector();
+ protected override SaturationValueSelector CreateSaturationValueSelector() => new OsuSaturationValueSelector();
+
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour osuColour)
+ {
+ Background.Colour = colourProvider?.Dark5 ?? osuColour.GreySeafoamDark;
+
+ Content.Padding = new MarginPadding(spacing);
+ Content.Spacing = new Vector2(0, spacing);
+ }
+
+ private static EdgeEffectParameters createShadowParameters() => new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Offset = new Vector2(0, 1),
+ Radius = 3,
+ Colour = Colour4.Black.Opacity(0.3f)
+ };
+
+ private class OsuHueSelector : HueSelector
+ {
+ public OsuHueSelector()
+ {
+ SliderBar.CornerRadius = corner_radius;
+ SliderBar.Masking = true;
+ }
+
+ protected override Drawable CreateSliderNub() => new SliderNub(this);
+
+ private class SliderNub : CompositeDrawable
+ {
+ private readonly Bindable hue;
+ private readonly Box fill;
+
+ public SliderNub(OsuHueSelector osuHueSelector)
+ {
+ hue = osuHueSelector.Hue.GetBoundCopy();
+
+ InternalChild = new CircularContainer
+ {
+ Height = 35,
+ Width = 10,
+ Origin = Anchor.Centre,
+ Anchor = Anchor.Centre,
+ Masking = true,
+ BorderColour = Colour4.White,
+ BorderThickness = control_border_thickness,
+ EdgeEffect = createShadowParameters(),
+ Child = fill = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ hue.BindValueChanged(h => fill.Colour = Colour4.FromHSV(h.NewValue, 1, 1), true);
+ }
+ }
+ }
+
+ private class OsuSaturationValueSelector : SaturationValueSelector
+ {
+ public OsuSaturationValueSelector()
+ {
+ SelectionArea.CornerRadius = corner_radius;
+ SelectionArea.Masking = true;
+ // purposefully use hard non-AA'd masking to avoid edge artifacts.
+ SelectionArea.MaskingSmoothness = 0;
+ }
+
+ protected override Marker CreateMarker() => new OsuMarker();
+
+ private class OsuMarker : Marker
+ {
+ private readonly Box previewBox;
+
+ public OsuMarker()
+ {
+ AutoSizeAxes = Axes.Both;
+
+ InternalChild = new CircularContainer
+ {
+ Size = new Vector2(20),
+ Masking = true,
+ BorderColour = Colour4.White,
+ BorderThickness = control_border_thickness,
+ EdgeEffect = createShadowParameters(),
+ Child = previewBox = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Current.BindValueChanged(colour => previewBox.Colour = colour.NewValue, true);
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs
new file mode 100644
index 0000000000..331a1b67c9
--- /dev/null
+++ b/osu.Game/Graphics/UserInterfaceV2/OsuHexColourPicker.cs
@@ -0,0 +1,57 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using JetBrains.Annotations;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.UserInterface;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Overlays;
+
+namespace osu.Game.Graphics.UserInterfaceV2
+{
+ public class OsuHexColourPicker : HexColourPicker
+ {
+ public OsuHexColourPicker()
+ {
+ Padding = new MarginPadding(20);
+ Spacing = 20;
+ }
+
+ [BackgroundDependencyLoader(true)]
+ private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour osuColour)
+ {
+ Background.Colour = overlayColourProvider?.Dark6 ?? osuColour.GreySeafoamDarker;
+ }
+
+ protected override TextBox CreateHexCodeTextBox() => new OsuTextBox();
+ protected override ColourPreview CreateColourPreview() => new OsuColourPreview();
+
+ private class OsuColourPreview : ColourPreview
+ {
+ private readonly Box preview;
+
+ public OsuColourPreview()
+ {
+ InternalChild = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Child = preview = new Box
+ {
+ RelativeSizeAxes = Axes.Both
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Current.BindValueChanged(colour => preview.Colour = colour.NewValue, true);
+ }
+ }
+ }
+}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 32136b8789..dcd2d68b43 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -223,7 +223,20 @@ namespace osu.Game
// bind config int to database RulesetInfo
configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset);
- Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First();
+
+ var preferredRuleset = RulesetStore.GetRuleset(configRuleset.Value);
+
+ try
+ {
+ Ruleset.Value = preferredRuleset ?? RulesetStore.AvailableRulesets.First();
+ }
+ catch (Exception e)
+ {
+ // on startup, a ruleset may be selected which has compatibility issues.
+ Logger.Error(e, $@"Failed to switch to preferred ruleset {preferredRuleset}.");
+ Ruleset.Value = RulesetStore.AvailableRulesets.First();
+ }
+
Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0;
// bind config int to database SkinInfo
@@ -478,6 +491,10 @@ namespace osu.Game
public override Task Import(params ImportTask[] imports)
{
// encapsulate task as we don't want to begin the import process until in a ready state.
+
+ // ReSharper disable once AsyncVoidLambda
+ // TODO: This is bad because `new Task` doesn't have a Func override.
+ // Only used for android imports and a bit of a mess. Probably needs rethinking overall.
var importTask = new Task(async () => await base.Import(imports).ConfigureAwait(false));
waitForReady(() => this, _ => importTask.Start());
diff --git a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs
index f8e1ac0c84..cb144defbf 100644
--- a/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogUpdateStreamItem.cs
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using Humanizer;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osuTK.Graphics;
@@ -17,11 +18,11 @@ namespace osu.Game.Overlays.Changelog
Width *= 2;
}
- protected override string MainText => Value.DisplayName;
+ protected override LocalisableString MainText => Value.DisplayName;
- protected override string AdditionalText => Value.LatestBuild.DisplayVersion;
+ protected override LocalisableString AdditionalText => Value.LatestBuild.DisplayVersion;
- protected override string InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null;
+ protected override LocalisableString InfoText => Value.LatestBuild.Users > 0 ? $"{"user".ToQuantity(Value.LatestBuild.Users, "N0")} online" : null;
protected override Color4 GetBarColour(OsuColour colours) => Value.Colour;
}
diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
index 3314ed957a..056d4ad6f7 100644
--- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
+++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
@@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.ComponentModel;
+using osu.Framework.Localisation;
+using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Dashboard
{
@@ -13,13 +16,14 @@ namespace osu.Game.Overlays.Dashboard
{
public DashboardTitle()
{
- Title = "dashboard";
+ Title = HomeStrings.UserTitle;
Description = "view your friends and other information";
IconTexture = "Icons/Hexacons/social";
}
}
}
+ [LocalisableEnum(typeof(DashboardOverlayTabsEnumLocalisationMapper))]
public enum DashboardOverlayTabs
{
Friends,
@@ -27,4 +31,22 @@ namespace osu.Game.Overlays.Dashboard
[Description("Currently Playing")]
CurrentlyPlaying
}
+
+ public class DashboardOverlayTabsEnumLocalisationMapper : EnumLocalisationMapper
+ {
+ public override LocalisableString Map(DashboardOverlayTabs value)
+ {
+ switch (value)
+ {
+ case DashboardOverlayTabs.Friends:
+ return FriendsStrings.TitleCompact;
+
+ case DashboardOverlayTabs.CurrentlyPlaying:
+ return @"Currently Playing";
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(value), value, null);
+ }
+ }
+ }
}
diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs
index 7e902203f8..11dcb93e6f 100644
--- a/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/FriendsOnlineStatusItem.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Extensions;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osuTK.Graphics;
@@ -14,9 +16,9 @@ namespace osu.Game.Overlays.Dashboard.Friends
{
}
- protected override string MainText => Value.Status.ToString();
+ protected override LocalisableString MainText => Value.Status.GetLocalisableDescription();
- protected override string AdditionalText => Value.Count.ToString();
+ protected override LocalisableString AdditionalText => Value.Count.ToString();
protected override Color4 GetBarColour(OsuColour colours)
{
diff --git a/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs
index 6f2f55a6ed..4b5a7ef066 100644
--- a/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/OnlineStatus.cs
@@ -1,12 +1,38 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using osu.Framework.Localisation;
+using osu.Game.Resources.Localisation.Web;
+
namespace osu.Game.Overlays.Dashboard.Friends
{
+ [LocalisableEnum(typeof(OnlineStatusEnumLocalisationMapper))]
public enum OnlineStatus
{
All,
Online,
Offline
}
+
+ public class OnlineStatusEnumLocalisationMapper : EnumLocalisationMapper
+ {
+ public override LocalisableString Map(OnlineStatus value)
+ {
+ switch (value)
+ {
+ case OnlineStatus.All:
+ return SortStrings.All;
+
+ case OnlineStatus.Online:
+ return UsersStrings.StatusOnline;
+
+ case OnlineStatus.Offline:
+ return UsersStrings.StatusOffline;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(value), value, null);
+ }
+ }
+ }
}
diff --git a/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs
index 3a5f65212d..dc756e2957 100644
--- a/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs
+++ b/osu.Game/Overlays/Dashboard/Friends/UserSortTabControl.cs
@@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.ComponentModel;
+using osu.Framework.Localisation;
+using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.Dashboard.Friends
{
@@ -9,6 +12,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
{
}
+ [LocalisableEnum(typeof(UserSortCriteriaEnumLocalisationMappper))]
public enum UserSortCriteria
{
[Description(@"Recently Active")]
@@ -16,4 +20,25 @@ namespace osu.Game.Overlays.Dashboard.Friends
Rank,
Username
}
+
+ public class UserSortCriteriaEnumLocalisationMappper : EnumLocalisationMapper
+ {
+ public override LocalisableString Map(UserSortCriteria value)
+ {
+ switch (value)
+ {
+ case UserSortCriteria.LastVisit:
+ return SortStrings.LastVisit;
+
+ case UserSortCriteria.Rank:
+ return SortStrings.Rank;
+
+ case UserSortCriteria.Username:
+ return SortStrings.Username;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(value), value, null);
+ }
+ }
+ }
}
diff --git a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs
index 0ece96b56c..c2268ff43c 100644
--- a/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs
+++ b/osu.Game/Overlays/OverlayPanelDisplayStyleControl.cs
@@ -12,6 +12,9 @@ using osu.Framework.Allocation;
using osuTK.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Localisation;
+using System;
+using osu.Game.Resources.Localisation.Web;
+using osu.Framework.Extensions;
namespace osu.Game.Overlays
{
@@ -57,7 +60,7 @@ namespace osu.Game.Overlays
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
- public LocalisableString TooltipText => $@"{Value} view";
+ public LocalisableString TooltipText => Value.GetLocalisableDescription();
private readonly SpriteIcon icon;
@@ -98,10 +101,32 @@ namespace osu.Game.Overlays
}
}
+ [LocalisableEnum(typeof(OverlayPanelDisplayStyleEnumLocalisationMapper))]
public enum OverlayPanelDisplayStyle
{
Card,
List,
Brick
}
+
+ public class OverlayPanelDisplayStyleEnumLocalisationMapper : EnumLocalisationMapper
+ {
+ public override LocalisableString Map(OverlayPanelDisplayStyle value)
+ {
+ switch (value)
+ {
+ case OverlayPanelDisplayStyle.Card:
+ return UsersStrings.ViewModeCard;
+
+ case OverlayPanelDisplayStyle.List:
+ return UsersStrings.ViewModeList;
+
+ case OverlayPanelDisplayStyle.Brick:
+ return UsersStrings.ViewModeBrick;
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(value), value, null);
+ }
+ }
+ }
}
diff --git a/osu.Game/Overlays/OverlayScrollContainer.cs b/osu.Game/Overlays/OverlayScrollContainer.cs
index c5b4cc3645..ca5fc90027 100644
--- a/osu.Game/Overlays/OverlayScrollContainer.cs
+++ b/osu.Game/Overlays/OverlayScrollContainer.cs
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;
@@ -118,7 +119,7 @@ namespace osu.Game.Overlays
}
});
- TooltipText = "Scroll to top";
+ TooltipText = CommonStrings.ButtonsBackToTop;
}
[BackgroundDependencyLoader]
diff --git a/osu.Game/Overlays/OverlayStreamItem.cs b/osu.Game/Overlays/OverlayStreamItem.cs
index cd1391a3d8..56502ff70f 100644
--- a/osu.Game/Overlays/OverlayStreamItem.cs
+++ b/osu.Game/Overlays/OverlayStreamItem.cs
@@ -12,6 +12,7 @@ using osu.Framework.Allocation;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics;
using osuTK.Graphics;
+using osu.Framework.Localisation;
namespace osu.Game.Overlays
{
@@ -88,11 +89,11 @@ namespace osu.Game.Overlays
SelectedItem.BindValueChanged(_ => updateState(), true);
}
- protected abstract string MainText { get; }
+ protected abstract LocalisableString MainText { get; }
- protected abstract string AdditionalText { get; }
+ protected abstract LocalisableString AdditionalText { get; }
- protected virtual string InfoText => string.Empty;
+ protected virtual LocalisableString InfoText => string.Empty;
protected abstract Color4 GetBarColour(OsuColour colours);
diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
index 179762103a..1a4f6087c7 100644
--- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
+++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownImageBlock.cs
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Containers.Markdown;
+using osu.Framework.Graphics.Sprites;
using osuTK;
namespace osu.Game.Overlays.Wiki.Markdown
@@ -32,11 +33,7 @@ namespace osu.Game.Overlays.Wiki.Markdown
{
Children = new Drawable[]
{
- new WikiMarkdownImage(linkInline)
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- },
+ new BlockMarkdownImage(linkInline),
parentTextComponent.CreateSpriteText().With(t =>
{
t.Text = linkInline.Title;
@@ -45,5 +42,50 @@ namespace osu.Game.Overlays.Wiki.Markdown
}),
};
}
+
+ private class BlockMarkdownImage : WikiMarkdownImage
+ {
+ public BlockMarkdownImage(LinkInline linkInline)
+ : base(linkInline)
+ {
+ AutoSizeAxes = Axes.Y;
+ RelativeSizeAxes = Axes.X;
+ }
+
+ protected override ImageContainer CreateImageContainer(string url) => new BlockImageContainer(url);
+
+ private class BlockImageContainer : ImageContainer
+ {
+ public BlockImageContainer(string url)
+ : base(url)
+ {
+ AutoSizeAxes = Axes.Y;
+ RelativeSizeAxes = Axes.X;
+ }
+
+ protected override Sprite CreateImageSprite() => new ImageSprite();
+
+ private class ImageSprite : Sprite
+ {
+ public ImageSprite()
+ {
+ Anchor = Anchor.TopCentre;
+ Origin = Anchor.TopCentre;
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (Width > Parent.DrawWidth)
+ {
+ float ratio = Height / Width;
+ Width = Parent.DrawWidth;
+ Height = ratio * Width;
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs
index 79d16013e3..6f00bb6c75 100644
--- a/osu.Game/Rulesets/Mods/Mod.cs
+++ b/osu.Game/Rulesets/Mods/Mod.cs
@@ -114,8 +114,8 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
public virtual bool UserPlayable => true;
- [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to true.")] // Can be removed 20211009
- public virtual bool IsRanked => false;
+ [Obsolete("Going forward, the concept of \"ranked\" doesn't exist. The only exceptions are automation mods, which should now override and set UserPlayable to false.")] // Can be removed 20211009
+ public virtual bool Ranked => false;
///
/// Whether this mod requires configuration to apply changes to the game.
diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
new file mode 100644
index 0000000000..b2e35d7020
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
@@ -0,0 +1,93 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Specialized;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Screens.Ranking.Expanded;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Components
+{
+ public class StarRatingRangeDisplay : OnlinePlayComposite
+ {
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ private StarRatingDisplay minDisplay;
+ private Drawable minBackground;
+ private StarRatingDisplay maxDisplay;
+ private Drawable maxBackground;
+
+ public StarRatingRangeDisplay()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = 1,
+ Children = new[]
+ {
+ minBackground = new Box
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.5f),
+ },
+ maxBackground = new Box
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.5f),
+ },
+ }
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ minDisplay = new StarRatingDisplay(default),
+ maxDisplay = new StarRatingDisplay(default)
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Playlist.BindCollectionChanged(updateRange, true);
+ }
+
+ private void updateRange(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarDifficulty).ToArray();
+
+ StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarDifficulty : 0, 0);
+ StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarDifficulty : 0, 0);
+
+ minDisplay.Current.Value = minDifficulty;
+ maxDisplay.Current.Value = maxDifficulty;
+
+ minBackground.Colour = colours.ForDifficultyRating(minDifficulty.DifficultyRating, true);
+ maxBackground.Colour = colours.ForDifficultyRating(maxDifficulty.DifficultyRating, true);
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index 8e59dc8579..5f135a3e90 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -93,7 +93,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
bool matchingFilter = true;
- matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset));
+ matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset));
if (!string.IsNullOrEmpty(criteria.SearchString))
matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase));
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs
index 9e1a020eca..20d12d62a3 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSpectatorPlayerClock.cs
@@ -34,7 +34,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
public void Stop() => IsRunning = false;
- public bool Seek(double position) => true;
+ public bool Seek(double position)
+ {
+ CurrentTime = position;
+ return true;
+ }
public void ResetSpeedAdjustments()
{
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs
index efc12eaaa5..94278a47b6 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/CatchUpSyncManager.cs
@@ -1,8 +1,11 @@
// 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.Diagnostics;
using System.Linq;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Timing;
@@ -28,16 +31,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
///
public const double MAXIMUM_START_DELAY = 15000;
+ public event Action ReadyToStart;
+
///
/// The master clock which is used to control the timing of all player clocks clocks.
///
public IAdjustableClock MasterClock { get; }
+ public IBindable MasterState => masterState;
+
///
/// The player clocks.
///
private readonly List playerClocks = new List();
+ private readonly Bindable masterState = new Bindable();
+
private bool hasStarted;
private double? firstStartAttemptTime;
@@ -46,7 +55,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
MasterClock = master;
}
- public void AddPlayerClock(ISpectatorPlayerClock clock) => playerClocks.Add(clock);
+ public void AddPlayerClock(ISpectatorPlayerClock clock)
+ {
+ Debug.Assert(!playerClocks.Contains(clock));
+ playerClocks.Add(clock);
+ }
public void RemovePlayerClock(ISpectatorPlayerClock clock) => playerClocks.Remove(clock);
@@ -62,8 +75,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
return;
}
- updateCatchup();
- updateMasterClock();
+ updatePlayerCatchup();
+ updateMasterState();
}
///
@@ -81,14 +94,20 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
int readyCount = playerClocks.Count(s => !s.WaitingOnFrames.Value);
if (readyCount == playerClocks.Count)
- return hasStarted = true;
+ return performStart();
if (readyCount > 0)
{
firstStartAttemptTime ??= Time.Current;
if (Time.Current - firstStartAttemptTime > MAXIMUM_START_DELAY)
- return hasStarted = true;
+ return performStart();
+ }
+
+ bool performStart()
+ {
+ ReadyToStart?.Invoke();
+ return hasStarted = true;
}
return false;
@@ -97,7 +116,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
///
/// Updates the catchup states of all player clocks clocks.
///
- private void updateCatchup()
+ private void updatePlayerCatchup()
{
for (int i = 0; i < playerClocks.Count; i++)
{
@@ -135,19 +154,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
}
///
- /// Updates the master clock's running state.
+ /// Updates the state of the master clock.
///
- private void updateMasterClock()
+ private void updateMasterState()
{
bool anyInSync = playerClocks.Any(s => !s.IsCatchingUp);
-
- if (MasterClock.IsRunning != anyInSync)
- {
- if (anyInSync)
- MasterClock.Start();
- else
- MasterClock.Stop();
- }
+ masterState.Value = anyInSync ? MasterClockState.Synchronised : MasterClockState.TooFarAhead;
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs
index bd698108f6..3c644ccb78 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ISyncManager.cs
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using osu.Framework.Bindables;
using osu.Framework.Timing;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
@@ -10,11 +12,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
///
public interface ISyncManager
{
+ ///
+ /// An event which is invoked when gameplay is ready to start.
+ ///
+ event Action ReadyToStart;
+
///
/// The master clock which player clocks should synchronise to.
///
IAdjustableClock MasterClock { get; }
+ ///
+ /// An event which is invoked when the state of is changed.
+ ///
+ IBindable MasterState { get; }
+
///
/// Adds an to manage.
///
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs
new file mode 100644
index 0000000000..8982d1669d
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MasterClockState.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
+{
+ public enum MasterClockState
+ {
+ ///
+ /// The master clock is synchronised with at least one player clock.
+ ///
+ Synchronised,
+
+ ///
+ /// The master clock is too far ahead of any player clock and needs to slow down.
+ ///
+ TooFarAhead
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
index 983daac909..2a2759e0dd 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
@@ -5,6 +5,7 @@ using System;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
+using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Multiplayer;
@@ -42,6 +43,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private PlayerGrid grid;
private MultiSpectatorLeaderboard leaderboard;
private PlayerArea currentAudioSource;
+ private bool canStartMasterClock;
///
/// Creates a new .
@@ -100,15 +102,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
Expanded = { Value = true },
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
- }, leaderboardContainer.Add);
+ }, l =>
+ {
+ foreach (var instance in instances)
+ leaderboard.AddClock(instance.UserId, instance.GameplayClock);
+
+ leaderboardContainer.Add(leaderboard);
+ });
}
protected override void LoadComplete()
{
base.LoadComplete();
- masterClockContainer.Stop();
masterClockContainer.Reset();
+ masterClockContainer.Stop();
+
+ syncManager.ReadyToStart += onReadyToStart;
+ syncManager.MasterState.BindValueChanged(onMasterStateChanged, true);
}
protected override void Update()
@@ -129,19 +140,45 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private bool isCandidateAudioSource([CanBeNull] ISpectatorPlayerClock clock)
=> clock?.IsRunning == true && !clock.IsCatchingUp && !clock.WaitingOnFrames.Value;
+ private void onReadyToStart()
+ {
+ // Seek the master clock to the gameplay time.
+ // This is chosen as the first available frame in the players' replays, which matches the seek by each individual SpectatorPlayer.
+ var startTime = instances.Where(i => i.Score != null)
+ .SelectMany(i => i.Score.Replay.Frames)
+ .Select(f => f.Time)
+ .DefaultIfEmpty(0)
+ .Min();
+
+ masterClockContainer.Seek(startTime);
+ masterClockContainer.Start();
+
+ // Although the clock has been started, this flag is set to allow for later synchronisation state changes to also be able to start it.
+ canStartMasterClock = true;
+ }
+
+ private void onMasterStateChanged(ValueChangedEvent state)
+ {
+ switch (state.NewValue)
+ {
+ case MasterClockState.Synchronised:
+ if (canStartMasterClock)
+ masterClockContainer.Start();
+
+ break;
+
+ case MasterClockState.TooFarAhead:
+ masterClockContainer.Stop();
+ break;
+ }
+ }
+
protected override void OnUserStateChanged(int userId, SpectatorState spectatorState)
{
}
protected override void StartGameplay(int userId, GameplayState gameplayState)
- {
- var instance = instances.Single(i => i.UserId == userId);
-
- instance.LoadScore(gameplayState.Score);
-
- syncManager.AddPlayerClock(instance.GameplayClock);
- leaderboard.AddClock(instance.UserId, instance.GameplayClock);
- }
+ => instances.Single(i => i.UserId == userId).LoadScore(gameplayState.Score);
protected override void EndGameplay(int userId)
{
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs
index fe79e5db72..95ccc08608 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
///
/// Whether a is loaded in the area.
///
- public bool PlayerLoaded => stack?.CurrentScreen is Player;
+ public bool PlayerLoaded => (stack?.CurrentScreen as Player)?.IsLoaded == true;
///
/// The user id this corresponds to.
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 58f60d14cf..97854ee12f 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -768,6 +768,7 @@ namespace osu.Game.Screens.Play
return false;
HasFailed = true;
+ Score.ScoreInfo.Passed = false;
// There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer)
// could process an extra frame after the GameplayClock is stopped.
@@ -950,6 +951,10 @@ namespace osu.Game.Screens.Play
{
screenSuspension?.Expire();
+ // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap.
+ if (prepareScoreForDisplayTask == null)
+ Score.ScoreInfo.Passed = false;
+
// EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous.
// To resolve test failures, forcefully end playing synchronously when this screen exits.
// Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method.
diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs
index d0ef4131dc..ef1087dd62 100644
--- a/osu.Game/Screens/Play/SoloPlayer.cs
+++ b/osu.Game/Screens/Play/SoloPlayer.cs
@@ -12,6 +12,16 @@ namespace osu.Game.Screens.Play
{
public class SoloPlayer : SubmittingPlayer
{
+ public SoloPlayer()
+ : this(null)
+ {
+ }
+
+ protected SoloPlayer(PlayerConfiguration configuration = null)
+ : base(configuration)
+ {
+ }
+
protected override APIRequest CreateTokenRequest()
{
if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId))
@@ -27,9 +37,11 @@ namespace osu.Game.Screens.Play
protected override APIRequest CreateSubmissionRequest(Score score, long token)
{
- Debug.Assert(Beatmap.Value.BeatmapInfo.OnlineBeatmapID != null);
+ var beatmap = score.ScoreInfo.Beatmap;
- int beatmapId = Beatmap.Value.BeatmapInfo.OnlineBeatmapID.Value;
+ Debug.Assert(beatmap.OnlineBeatmapID != null);
+
+ int beatmapId = beatmap.OnlineBeatmapID.Value;
return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo);
}
diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs
index 0286b6b8a6..f662a479ec 100644
--- a/osu.Game/Screens/Play/SpectatorPlayer.cs
+++ b/osu.Game/Screens/Play/SpectatorPlayer.cs
@@ -47,8 +47,9 @@ namespace osu.Game.Screens.Play
{
base.StartGameplay();
+ // Start gameplay along with the very first arrival frame (the latest one).
+ score.Replay.Frames.Clear();
spectatorClient.OnNewFrames += userSentFrames;
- seekToGameplay();
}
private void userSentFrames(int userId, FrameDataBundle bundle)
@@ -62,6 +63,8 @@ namespace osu.Game.Screens.Play
if (!this.IsCurrentScreen())
return;
+ bool isFirstBundle = score.Replay.Frames.Count == 0;
+
foreach (var frame in bundle.Frames)
{
IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame();
@@ -73,19 +76,8 @@ namespace osu.Game.Screens.Play
score.Replay.Frames.Add(convertedFrame);
}
- seekToGameplay();
- }
-
- private bool seekedToGameplay;
-
- private void seekToGameplay()
- {
- if (seekedToGameplay || score.Replay.Frames.Count == 0)
- return;
-
- NonFrameStableSeek(score.Replay.Frames[0].Time);
-
- seekedToGameplay = true;
+ if (isFirstBundle && score.Replay.Frames.Count > 0)
+ NonFrameStableSeek(score.Replay.Frames[0].Time);
}
protected override Score CreateScore() => score;
diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs
index b843915a7c..7c5a06707d 100644
--- a/osu.Game/Screens/Play/SubmittingPlayer.cs
+++ b/osu.Game/Screens/Play/SubmittingPlayer.cs
@@ -27,6 +27,8 @@ namespace osu.Game.Screens.Play
[Resolved]
private IAPIProvider api { get; set; }
+ private TaskCompletionSource scoreSubmissionSource;
+
protected SubmittingPlayer(PlayerConfiguration configuration = null)
: base(configuration)
{
@@ -106,27 +108,16 @@ namespace osu.Game.Screens.Play
{
await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false);
- // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure).
- if (token == null)
- return;
+ await submitScore(score).ConfigureAwait(false);
+ }
- var tcs = new TaskCompletionSource();
- var request = CreateSubmissionRequest(score, token.Value);
+ public override bool OnExiting(IScreen next)
+ {
+ var exiting = base.OnExiting(next);
- request.Success += s =>
- {
- score.ScoreInfo.OnlineScoreID = s.ID;
- tcs.SetResult(true);
- };
+ submitScore(Score);
- request.Failure += e =>
- {
- Logger.Error(e, "Failed to submit score");
- tcs.SetResult(false);
- };
-
- api.Queue(request);
- await tcs.Task.ConfigureAwait(false);
+ return exiting;
}
///
@@ -143,5 +134,33 @@ namespace osu.Game.Screens.Play
/// The score to be submitted.
/// The submission token.
protected abstract APIRequest CreateSubmissionRequest(Score score, long token);
+
+ private Task submitScore(Score score)
+ {
+ // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure).
+ if (token == null)
+ return Task.CompletedTask;
+
+ if (scoreSubmissionSource != null)
+ return scoreSubmissionSource.Task;
+
+ scoreSubmissionSource = new TaskCompletionSource();
+ var request = CreateSubmissionRequest(score, token.Value);
+
+ request.Success += s =>
+ {
+ score.ScoreInfo.OnlineScoreID = s.ID;
+ scoreSubmissionSource.SetResult(true);
+ };
+
+ request.Failure += e =>
+ {
+ Logger.Error(e, "Failed to submit score");
+ scoreSubmissionSource.SetResult(false);
+ };
+
+ api.Queue(request);
+ return scoreSubmissionSource.Task;
+ }
}
}
diff --git a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs
index 7aba699216..e59a0de316 100644
--- a/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs
+++ b/osu.Game/Screens/Ranking/Expanded/StarRatingDisplay.cs
@@ -4,9 +4,7 @@
using System.Globalization;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
@@ -111,12 +109,9 @@ namespace osu.Game.Screens.Ranking.Expanded
var rating = Current.Value.DifficultyRating;
- background.Colour = rating == DifficultyRating.ExpertPlus
- ? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959"))
- : (ColourInfo)colours.ForDifficultyRating(rating);
+ background.Colour = colours.ForDifficultyRating(rating, true);
textFlow.Clear();
-
textFlow.AddText($"{wholePart}", s =>
{
s.Colour = Color4.Black;
diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
index 23f36ffe5b..7a35c8600d 100644
--- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
+++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs
@@ -127,6 +127,7 @@ namespace osu.Game.Skinning.Editor
public override bool HandleFlip(Direction direction)
{
var selectionQuad = getSelectionQuad();
+ Vector2 scaleFactor = direction == Direction.Horizontal ? new Vector2(-1, 1) : new Vector2(1, -1);
foreach (var b in SelectedBlueprints)
{
@@ -136,10 +137,8 @@ namespace osu.Game.Skinning.Editor
updateDrawablePosition(drawableItem, flippedPosition);
- drawableItem.Scale *= new Vector2(
- direction == Direction.Horizontal ? -1 : 1,
- direction == Direction.Vertical ? -1 : 1
- );
+ drawableItem.Scale *= scaleFactor;
+ drawableItem.Rotation -= drawableItem.Rotation % 180 * 2;
}
return true;
diff --git a/osu.Game/Skinning/ResourceStoreBackedSkin.cs b/osu.Game/Skinning/ResourceStoreBackedSkin.cs
new file mode 100644
index 0000000000..f041b82cf4
--- /dev/null
+++ b/osu.Game/Skinning/ResourceStoreBackedSkin.cs
@@ -0,0 +1,57 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+using System;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.IO.Stores;
+using osu.Framework.Platform;
+using osu.Game.Audio;
+
+namespace osu.Game.Skinning
+{
+ ///
+ /// An that uses an underlying with namespaces for resources retrieval.
+ ///
+ public class ResourceStoreBackedSkin : ISkin, IDisposable
+ {
+ private readonly TextureStore textures;
+ private readonly ISampleStore samples;
+
+ public ResourceStoreBackedSkin(IResourceStore resources, GameHost host, AudioManager audio)
+ {
+ textures = new TextureStore(host.CreateTextureLoaderStore(new NamespacedResourceStore(resources, @"Textures")));
+ samples = audio.GetSampleStore(new NamespacedResourceStore(resources, @"Samples"));
+ }
+
+ public Drawable? GetDrawableComponent(ISkinComponent component) => null;
+
+ public Texture? GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => textures.Get(componentName, wrapModeS, wrapModeT);
+
+ public ISample? GetSample(ISampleInfo sampleInfo)
+ {
+ foreach (var lookup in sampleInfo.LookupNames)
+ {
+ ISample? sample = samples.Get(lookup);
+ if (sample != null)
+ return sample;
+ }
+
+ return null;
+ }
+
+ public IBindable? GetConfig(TLookup lookup) => null;
+
+ public void Dispose()
+ {
+ textures.Dispose();
+ samples.Dispose();
+ }
+ }
+}
diff --git a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs
index cb8b0fb3c8..19efc66814 100644
--- a/osu.Game/Skinning/RulesetSkinProvidingContainer.cs
+++ b/osu.Game/Skinning/RulesetSkinProvidingContainer.cs
@@ -1,10 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
+using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.IO.Stores;
+using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.UI;
@@ -44,11 +48,16 @@ namespace osu.Game.Skinning
private ISkinSource parentSource;
+ private ResourceStoreBackedSkin rulesetResourcesSkin;
+
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
parentSource = parent.Get();
parentSource.SourceChanged += OnSourceChanged;
+ if (Ruleset.CreateResourceStore() is IResourceStore resources)
+ rulesetResourcesSkin = new ResourceStoreBackedSkin(resources, parent.Get(), parent.Get());
+
// ensure sources are populated and ready for use before childrens' asynchronous load flow.
UpdateSkinSources();
@@ -78,6 +87,16 @@ namespace osu.Game.Skinning
break;
}
}
+
+ int lastDefaultSkinIndex = SkinSources.IndexOf(SkinSources.OfType().LastOrDefault());
+
+ // Ruleset resources should be given the ability to override game-wide defaults
+ // This is achieved by placing them before the last instance of DefaultSkin.
+ // Note that DefaultSkin may not be present in some test scenes.
+ if (lastDefaultSkinIndex >= 0)
+ SkinSources.Insert(lastDefaultSkinIndex, rulesetResourcesSkin);
+ else
+ SkinSources.Add(rulesetResourcesSkin);
}
protected ISkin GetLegacyRulesetTransformedSkin(ISkin legacySkin)
@@ -98,6 +117,8 @@ namespace osu.Game.Skinning
if (parentSource != null)
parentSource.SourceChanged -= OnSourceChanged;
+
+ rulesetResourcesSkin?.Dispose();
}
}
}
diff --git a/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs
new file mode 100644
index 0000000000..204c189591
--- /dev/null
+++ b/osu.Game/Tests/Visual/Multiplayer/IMultiplayerTestSceneDependencies.cs
@@ -0,0 +1,37 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Database;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Screens.OnlinePlay;
+using osu.Game.Tests.Visual.OnlinePlay;
+using osu.Game.Tests.Visual.Spectator;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ ///
+ /// Interface that defines the dependencies required for multiplayer test scenes.
+ ///
+ public interface IMultiplayerTestSceneDependencies : IOnlinePlayTestSceneDependencies
+ {
+ ///
+ /// The cached .
+ ///
+ TestMultiplayerClient Client { get; }
+
+ ///
+ /// The cached .
+ ///
+ new TestMultiplayerRoomManager RoomManager { get; }
+
+ ///
+ /// The cached .
+ ///
+ TestUserLookupCache LookupCache { get; }
+
+ ///
+ /// The cached .
+ ///
+ TestSpectatorClient SpectatorClient { get; }
+ }
+}
diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
index c76d1053b2..b7d3793ab1 100644
--- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs
@@ -2,66 +2,55 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
-using osu.Game.Screens.OnlinePlay;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Beatmaps;
+using osu.Game.Tests.Visual.OnlinePlay;
+using osu.Game.Tests.Visual.Spectator;
namespace osu.Game.Tests.Visual.Multiplayer
{
- public abstract class MultiplayerTestScene : RoomTestScene
+ ///
+ /// The base test scene for all multiplayer components and screens.
+ ///
+ public abstract class MultiplayerTestScene : OnlinePlayTestScene, IMultiplayerTestSceneDependencies
{
public const int PLAYER_1_ID = 55;
public const int PLAYER_2_ID = 56;
- [Cached(typeof(MultiplayerClient))]
- public TestMultiplayerClient Client { get; }
+ public TestMultiplayerClient Client => OnlinePlayDependencies.Client;
+ public new TestMultiplayerRoomManager RoomManager => OnlinePlayDependencies.RoomManager;
+ public TestUserLookupCache LookupCache => OnlinePlayDependencies?.LookupCache;
+ public TestSpectatorClient SpectatorClient => OnlinePlayDependencies?.SpectatorClient;
- [Cached(typeof(IRoomManager))]
- public TestMultiplayerRoomManager RoomManager { get; }
-
- [Cached]
- public Bindable Filter { get; }
-
- [Cached]
- public OngoingOperationTracker OngoingOperationTracker { get; }
-
- protected override Container Content => content;
- private readonly TestMultiplayerRoomContainer content;
+ protected new MultiplayerTestSceneDependencies OnlinePlayDependencies => (MultiplayerTestSceneDependencies)base.OnlinePlayDependencies;
private readonly bool joinRoom;
protected MultiplayerTestScene(bool joinRoom = true)
{
this.joinRoom = joinRoom;
- base.Content.Add(content = new TestMultiplayerRoomContainer { RelativeSizeAxes = Axes.Both });
-
- Client = content.Client;
- RoomManager = content.RoomManager;
- Filter = content.Filter;
- OngoingOperationTracker = content.OngoingOperationTracker;
}
[SetUp]
public new void Setup() => Schedule(() =>
{
- RoomManager.Schedule(() => RoomManager.PartRoom());
-
if (joinRoom)
{
- Room.Name.Value = "test name";
- Room.Playlist.Add(new PlaylistItem
+ var room = new Room
{
- Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
- Ruleset = { Value = Ruleset.Value }
- });
+ Name = { Value = "test name" },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
+ Ruleset = { Value = Ruleset.Value }
+ }
+ }
+ };
- RoomManager.Schedule(() => RoomManager.CreateRoom(Room));
+ RoomManager.CreateRoom(room);
+ SelectedRoom.Value = room;
}
});
@@ -72,5 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
if (joinRoom)
AddUntilStep("wait for room join", () => Client.Room != null);
}
+
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies();
}
}
diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs
new file mode 100644
index 0000000000..a2b0b066a7
--- /dev/null
+++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestSceneDependencies.cs
@@ -0,0 +1,38 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Database;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Spectator;
+using osu.Game.Screens.OnlinePlay;
+using osu.Game.Tests.Visual.OnlinePlay;
+using osu.Game.Tests.Visual.Spectator;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ ///
+ /// Contains the basic dependencies of multiplayer test scenes.
+ ///
+ public class MultiplayerTestSceneDependencies : OnlinePlayTestSceneDependencies, IMultiplayerTestSceneDependencies
+ {
+ public TestMultiplayerClient Client { get; }
+ public TestUserLookupCache LookupCache { get; }
+ public TestSpectatorClient SpectatorClient { get; }
+ public new TestMultiplayerRoomManager RoomManager => (TestMultiplayerRoomManager)base.RoomManager;
+
+ public MultiplayerTestSceneDependencies()
+ {
+ Client = new TestMultiplayerClient(RoomManager);
+ LookupCache = new TestUserLookupCache();
+ SpectatorClient = CreateSpectatorClient();
+
+ CacheAs(Client);
+ CacheAs(LookupCache);
+ CacheAs(SpectatorClient);
+ }
+
+ protected override IRoomManager CreateRoomManager() => new TestMultiplayerRoomManager();
+
+ protected virtual TestSpectatorClient CreateSpectatorClient() => new TestSpectatorClient();
+ }
+}
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
index b12bd8091d..b0c8d6d19b 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs
@@ -20,6 +20,9 @@ using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
+ ///
+ /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a .
+ ///
public class TestMultiplayerClient : MultiplayerClient
{
public override IBindable IsConnected => isConnected;
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs
deleted file mode 100644
index 1abf4d8f5d..0000000000
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomContainer.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Online.Multiplayer;
-using osu.Game.Screens.OnlinePlay;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public class TestMultiplayerRoomContainer : Container
- {
- protected override Container Content => content;
- private readonly Container content;
-
- [Cached(typeof(MultiplayerClient))]
- public readonly TestMultiplayerClient Client;
-
- [Cached(typeof(IRoomManager))]
- public readonly TestMultiplayerRoomManager RoomManager;
-
- [Cached]
- public readonly Bindable Filter = new Bindable(new FilterCriteria());
-
- [Cached]
- public readonly OngoingOperationTracker OngoingOperationTracker;
-
- public TestMultiplayerRoomContainer()
- {
- RelativeSizeAxes = Axes.Both;
-
- RoomManager = new TestMultiplayerRoomManager();
- Client = new TestMultiplayerClient(RoomManager);
- OngoingOperationTracker = new OngoingOperationTracker();
-
- AddRangeInternal(new Drawable[]
- {
- Client,
- RoomManager,
- OngoingOperationTracker,
- content = new Container { RelativeSizeAxes = Axes.Both }
- });
- }
- }
-}
diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs
index 315be510a3..5d66cdba02 100644
--- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs
+++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerRoomManager.cs
@@ -11,11 +11,15 @@ using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
+using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Multiplayer;
namespace osu.Game.Tests.Visual.Multiplayer
{
+ ///
+ /// A for use in multiplayer test scenes. Should generally not be used by itself outside of a .
+ ///
public class TestMultiplayerRoomManager : MultiplayerRoomManager
{
[Resolved]
@@ -29,10 +33,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
public new readonly List Rooms = new List();
- protected override void LoadComplete()
+ [BackgroundDependencyLoader]
+ private void load()
{
- base.LoadComplete();
-
int currentScoreId = 0;
int currentRoomId = 0;
int currentPlaylistItemId = 0;
diff --git a/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs
new file mode 100644
index 0000000000..813e617ac5
--- /dev/null
+++ b/osu.Game/Tests/Visual/OnlinePlay/BasicTestRoomManager.cs
@@ -0,0 +1,78 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Bindables;
+using osu.Game.Beatmaps;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets;
+using osu.Game.Screens.OnlinePlay;
+using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.OnlinePlay
+{
+ ///
+ /// A very simple for use in online play test scenes.
+ ///
+ public class BasicTestRoomManager : IRoomManager
+ {
+ public event Action RoomsUpdated
+ {
+ add { }
+ remove { }
+ }
+
+ public readonly BindableList Rooms = new BindableList();
+
+ public IBindable InitialRoomsReceived { get; } = new Bindable(true);
+
+ IBindableList IRoomManager.Rooms => Rooms;
+
+ public void CreateRoom(Room room, Action onSuccess = null, Action onError = null)
+ {
+ room.RoomID.Value ??= Rooms.Select(r => r.RoomID.Value).Where(id => id != null).Select(id => id.Value).DefaultIfEmpty().Max() + 1;
+ Rooms.Add(room);
+ onSuccess?.Invoke(room);
+ }
+
+ public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => onSuccess?.Invoke(room);
+
+ public void PartRoom()
+ {
+ }
+
+ public void AddRooms(int count, RulesetInfo ruleset = null)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ var room = new Room
+ {
+ RoomID = { Value = i },
+ Name = { Value = $"Room {i}" },
+ Host = { Value = new User { Username = "Host" } },
+ EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
+ Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }
+ };
+
+ if (ruleset != null)
+ {
+ room.Playlist.Add(new PlaylistItem
+ {
+ Ruleset = { Value = ruleset },
+ Beatmap =
+ {
+ Value = new BeatmapInfo
+ {
+ Metadata = new BeatmapMetadata()
+ }
+ }
+ });
+ }
+
+ CreateRoom(room);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.cs
new file mode 100644
index 0000000000..6e1e831d9b
--- /dev/null
+++ b/osu.Game/Tests/Visual/OnlinePlay/IOnlinePlayTestSceneDependencies.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.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay;
+using osu.Game.Screens.OnlinePlay.Lounge.Components;
+
+namespace osu.Game.Tests.Visual.OnlinePlay
+{
+ ///
+ /// Interface that defines the dependencies required for online play test scenes.
+ ///
+ public interface IOnlinePlayTestSceneDependencies
+ {
+ ///
+ /// The cached .
+ ///
+ Bindable SelectedRoom { get; }
+
+ ///
+ /// The cached
+ ///
+ IRoomManager RoomManager { get; }
+
+ ///
+ /// The cached .
+ ///
+ Bindable Filter { get; }
+
+ ///
+ /// The cached .
+ ///
+ OngoingOperationTracker OngoingOperationTracker { get; }
+
+ ///
+ /// The cached .
+ ///
+ OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; }
+ }
+}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs
new file mode 100644
index 0000000000..997c910dd4
--- /dev/null
+++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs
@@ -0,0 +1,104 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay;
+using osu.Game.Screens.OnlinePlay.Lounge.Components;
+
+namespace osu.Game.Tests.Visual.OnlinePlay
+{
+ ///
+ /// A base test scene for all online play components and screens.
+ ///
+ public abstract class OnlinePlayTestScene : ScreenTestScene, IOnlinePlayTestSceneDependencies
+ {
+ public Bindable SelectedRoom => OnlinePlayDependencies?.SelectedRoom;
+ public IRoomManager RoomManager => OnlinePlayDependencies?.RoomManager;
+ public Bindable Filter => OnlinePlayDependencies?.Filter;
+ public OngoingOperationTracker OngoingOperationTracker => OnlinePlayDependencies?.OngoingOperationTracker;
+ public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker => OnlinePlayDependencies?.AvailabilityTracker;
+
+ ///
+ /// All dependencies required for online play components and screens.
+ ///
+ protected OnlinePlayTestSceneDependencies OnlinePlayDependencies => dependencies?.OnlinePlayDependencies;
+
+ private DelegatedDependencyContainer dependencies;
+
+ protected override Container Content => content;
+ private readonly Container content;
+ private readonly Container drawableDependenciesContainer;
+
+ protected OnlinePlayTestScene()
+ {
+ base.Content.AddRange(new Drawable[]
+ {
+ drawableDependenciesContainer = new Container { RelativeSizeAxes = Axes.Both },
+ content = new Container { RelativeSizeAxes = Axes.Both },
+ });
+ }
+
+ protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
+ {
+ dependencies = new DelegatedDependencyContainer(base.CreateChildDependencies(parent));
+ return dependencies;
+ }
+
+ [SetUp]
+ public void Setup() => Schedule(() =>
+ {
+ // Reset the room dependencies to a fresh state.
+ drawableDependenciesContainer.Clear();
+ dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies();
+ drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents);
+ });
+
+ ///
+ /// Creates the room dependencies. Called every .
+ ///
+ ///
+ /// Any custom dependencies required for online play sub-classes should be added here.
+ ///
+ protected virtual OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new OnlinePlayTestSceneDependencies();
+
+ ///
+ /// A providing a mutable lookup source for online play dependencies.
+ ///
+ private class DelegatedDependencyContainer : IReadOnlyDependencyContainer
+ {
+ ///
+ /// The online play dependencies.
+ ///
+ public OnlinePlayTestSceneDependencies OnlinePlayDependencies { get; set; }
+
+ private readonly IReadOnlyDependencyContainer parent;
+ private readonly DependencyContainer injectableDependencies;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The fallback to use when cannot satisfy a dependency.
+ public DelegatedDependencyContainer(IReadOnlyDependencyContainer parent)
+ {
+ this.parent = parent;
+ injectableDependencies = new DependencyContainer(this);
+ }
+
+ public object Get(Type type)
+ => OnlinePlayDependencies?.Get(type) ?? parent.Get(type);
+
+ public object Get(Type type, CacheInfo info)
+ => OnlinePlayDependencies?.Get(type, info) ?? parent.Get(type, info);
+
+ public void Inject(T instance)
+ where T : class
+ => injectableDependencies.Inject(instance);
+ }
+ }
+}
diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
new file mode 100644
index 0000000000..ddbbfe501b
--- /dev/null
+++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestSceneDependencies.cs
@@ -0,0 +1,78 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay;
+using osu.Game.Screens.OnlinePlay.Lounge.Components;
+
+namespace osu.Game.Tests.Visual.OnlinePlay
+{
+ ///
+ /// Contains the basic dependencies of online play test scenes.
+ ///
+ public class OnlinePlayTestSceneDependencies : IReadOnlyDependencyContainer, IOnlinePlayTestSceneDependencies
+ {
+ public Bindable SelectedRoom { get; }
+ public IRoomManager RoomManager { get; }
+ public Bindable Filter { get; }
+ public OngoingOperationTracker OngoingOperationTracker { get; }
+ public OnlinePlayBeatmapAvailabilityTracker AvailabilityTracker { get; }
+
+ ///
+ /// All cached dependencies which are also components.
+ ///
+ public IReadOnlyList DrawableComponents => drawableComponents;
+
+ private readonly List drawableComponents = new List();
+ private readonly DependencyContainer dependencies;
+
+ public OnlinePlayTestSceneDependencies()
+ {
+ SelectedRoom = new Bindable();
+ RoomManager = CreateRoomManager();
+ Filter = new Bindable(new FilterCriteria());
+ OngoingOperationTracker = new OngoingOperationTracker();
+ AvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
+
+ dependencies = new DependencyContainer(new CachedModelDependencyContainer(null) { Model = { BindTarget = SelectedRoom } });
+
+ CacheAs(SelectedRoom);
+ CacheAs(RoomManager);
+ CacheAs(Filter);
+ CacheAs(OngoingOperationTracker);
+ CacheAs(AvailabilityTracker);
+ }
+
+ public object Get(Type type)
+ => dependencies.Get(type);
+
+ public object Get(Type type, CacheInfo info)
+ => dependencies.Get(type, info);
+
+ public void Inject(T instance)
+ where T : class
+ => dependencies.Inject(instance);
+
+ protected void Cache(object instance)
+ {
+ dependencies.Cache(instance);
+ if (instance is Drawable drawable)
+ drawableComponents.Add(drawable);
+ }
+
+ protected void CacheAs(T instance)
+ where T : class
+ {
+ dependencies.CacheAs(instance);
+ if (instance is Drawable drawable)
+ drawableComponents.Add(drawable);
+ }
+
+ protected virtual IRoomManager CreateRoomManager() => new BasicTestRoomManager();
+ }
+}
diff --git a/osu.Game/Tests/Visual/RoomTestScene.cs b/osu.Game/Tests/Visual/RoomTestScene.cs
deleted file mode 100644
index aaf5c7624f..0000000000
--- a/osu.Game/Tests/Visual/RoomTestScene.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using NUnit.Framework;
-using osu.Framework.Allocation;
-using osu.Framework.Bindables;
-using osu.Game.Online.Rooms;
-
-namespace osu.Game.Tests.Visual
-{
- public abstract class RoomTestScene : ScreenTestScene
- {
- [Cached]
- private readonly Bindable currentRoom = new Bindable();
-
- protected Room Room => currentRoom.Value;
-
- private CachedModelDependencyContainer dependencies;
-
- protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
- {
- dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent));
- dependencies.Model.BindTo(currentRoom);
- return dependencies;
- }
-
- [SetUp]
- public void Setup() => Schedule(() =>
- {
- currentRoom.Value = new Room();
- });
- }
-}
diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
index c7aa43b377..f206d4f8b0 100644
--- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
+++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs
@@ -20,9 +20,15 @@ namespace osu.Game.Tests.Visual.Spectator
{
public class TestSpectatorClient : SpectatorClient
{
+ ///
+ /// Maximum number of frames sent per bundle via .
+ ///
+ public const int FRAME_BUNDLE_SIZE = 10;
+
public override IBindable IsConnected { get; } = new Bindable(true);
private readonly Dictionary userBeatmapDictionary = new Dictionary();
+ private readonly Dictionary userNextFrameDictionary = new Dictionary();
[Resolved]
private IAPIProvider api { get; set; } = null!;
@@ -35,6 +41,7 @@ namespace osu.Game.Tests.Visual.Spectator
public void StartPlay(int userId, int beatmapId)
{
userBeatmapDictionary[userId] = beatmapId;
+ userNextFrameDictionary[userId] = 0;
sendPlayingState(userId);
}
@@ -57,24 +64,41 @@ namespace osu.Game.Tests.Visual.Spectator
public new void Schedule(Action action) => base.Schedule(action);
///
- /// Sends frames for an arbitrary user.
+ /// Sends frames for an arbitrary user, in bundles containing 10 frames each.
///
/// The user to send frames for.
- /// The frame index.
- /// The number of frames to send.
- public void SendFrames(int userId, int index, int count)
+ /// The total number of frames to send.
+ public void SendFrames(int userId, int count)
{
var frames = new List();
- for (int i = index; i < index + count; i++)
- {
- var buttonState = i == index + count - 1 ? ReplayButtonState.None : ReplayButtonState.Left1;
+ int currentFrameIndex = userNextFrameDictionary[userId];
+ int lastFrameIndex = currentFrameIndex + count - 1;
- frames.Add(new LegacyReplayFrame(i * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
+ for (; currentFrameIndex <= lastFrameIndex; currentFrameIndex++)
+ {
+ // This is done in the next frame so that currentFrameIndex is updated to the correct value.
+ if (frames.Count == FRAME_BUNDLE_SIZE)
+ flush();
+
+ var buttonState = currentFrameIndex == lastFrameIndex ? ReplayButtonState.None : ReplayButtonState.Left1;
+ frames.Add(new LegacyReplayFrame(currentFrameIndex * 100, RNG.Next(0, 512), RNG.Next(0, 512), buttonState));
}
- var bundle = new FrameDataBundle(new ScoreInfo { Combo = index + count }, frames);
- ((ISpectatorClient)this).UserSentFrames(userId, bundle);
+ flush();
+
+ userNextFrameDictionary[userId] = currentFrameIndex;
+
+ void flush()
+ {
+ if (frames.Count == 0)
+ return;
+
+ var bundle = new FrameDataBundle(new ScoreInfo { Combo = currentFrameIndex }, frames.ToArray());
+ ((ISpectatorClient)this).UserSentFrames(userId, bundle);
+
+ frames.Clear();
+ }
}
protected override Task BeginPlayingInternal(SpectatorState state)
diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs
index 09da4db952..5e5f20b307 100644
--- a/osu.Game/Tests/Visual/TestPlayer.cs
+++ b/osu.Game/Tests/Visual/TestPlayer.cs
@@ -1,14 +1,18 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Game.Online.API;
+using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
+using osu.Game.Scoring;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
@@ -16,7 +20,7 @@ namespace osu.Game.Tests.Visual
///
/// A player that exposes many components that would otherwise not be available, for testing purposes.
///
- public class TestPlayer : Player
+ public class TestPlayer : SoloPlayer
{
protected override bool PauseOnFocusLost { get; }
@@ -35,6 +39,10 @@ namespace osu.Game.Tests.Visual
public new HealthProcessor HealthProcessor => base.HealthProcessor;
+ public bool TokenCreationRequested { get; private set; }
+
+ public Score SubmittedScore { get; private set; }
+
public new bool PauseCooldownActive => base.PauseCooldownActive;
public readonly List Results = new List();
@@ -49,6 +57,20 @@ namespace osu.Game.Tests.Visual
PauseOnFocusLost = pauseOnFocusLost;
}
+ protected override bool HandleTokenRetrievalFailure(Exception exception) => false;
+
+ protected override APIRequest CreateTokenRequest()
+ {
+ TokenCreationRequested = true;
+ return base.CreateTokenRequest();
+ }
+
+ protected override APIRequest CreateSubmissionRequest(Score score, long token)
+ {
+ SubmittedScore = score;
+ return base.CreateSubmissionRequest(score, token);
+ }
+
protected override void PrepareReplay()
{
// Generally, replay generation is handled by whatever is constructing the player.
diff --git a/osu.Game/Tests/Visual/TestUserLookupCache.cs b/osu.Game/Tests/Visual/TestUserLookupCache.cs
new file mode 100644
index 0000000000..d2941b5bd5
--- /dev/null
+++ b/osu.Game/Tests/Visual/TestUserLookupCache.cs
@@ -0,0 +1,19 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Threading;
+using System.Threading.Tasks;
+using osu.Game.Database;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual
+{
+ public class TestUserLookupCache : UserLookupCache
+ {
+ protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) => Task.FromResult(new User
+ {
+ Id = lookup,
+ Username = $"User {lookup}"
+ });
+ }
+}
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index f047859dbb..14efcd516e 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -20,26 +20,26 @@
-
-
+
+
-
-
-
+
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 304047ad12..0ab0b26430 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -89,16 +89,16 @@
-
+
-
-
+
+
-
+
diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings
index 62751cebb1..d2c5b1223c 100644
--- a/osu.sln.DotSettings
+++ b/osu.sln.DotSettings
@@ -308,6 +308,7 @@
GL
GLSL
HID
+ HSV
HTML
HUD
ID