diff --git a/README.md b/README.md
index 016bd7d922..8f922f74a7 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
# osu!
-[](https://ci.appveyor.com/project/peppy/osu)
+[](https://github.com/ppy/osu/actions/workflows/ci.yml)
[](https://github.com/ppy/osu/releases/latest)
[](https://www.codefactor.io/repository/github/ppy/osu)
[](https://discord.gg/ppy)
diff --git a/osu.Android.props b/osu.Android.props
index f8cd4e41d4..ec223f98c2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
new file mode 100644
index 0000000000..fbbfee6b60
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
@@ -0,0 +1,120 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Utils;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Tests.Mods
+{
+ [TestFixture]
+ public class CatchModMirrorTest
+ {
+ [Test]
+ public void TestModMirror()
+ {
+ IBeatmap original = createBeatmap(false);
+ IBeatmap mirrored = createBeatmap(true);
+
+ assertEffectivePositionsMirrored(original, mirrored);
+ }
+
+ private static IBeatmap createBeatmap(bool withMirrorMod)
+ {
+ var beatmap = createRawBeatmap();
+ var mirrorMod = new CatchModMirror();
+
+ var beatmapProcessor = new CatchBeatmapProcessor(beatmap);
+ beatmapProcessor.PreProcess();
+
+ foreach (var hitObject in beatmap.HitObjects)
+ hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+
+ beatmapProcessor.PostProcess();
+
+ if (withMirrorMod)
+ mirrorMod.ApplyToBeatmap(beatmap);
+
+ return beatmap;
+ }
+
+ private static IBeatmap createRawBeatmap() => new Beatmap
+ {
+ HitObjects = new List
+ {
+ new Fruit
+ {
+ OriginalX = 150,
+ StartTime = 0
+ },
+ new Fruit
+ {
+ OriginalX = 450,
+ StartTime = 500
+ },
+ new JuiceStream
+ {
+ OriginalX = 250,
+ Path = new SliderPath
+ {
+ ControlPoints =
+ {
+ new PathControlPoint(new Vector2(-100, 1)),
+ new PathControlPoint(new Vector2(0, 2)),
+ new PathControlPoint(new Vector2(100, 3)),
+ new PathControlPoint(new Vector2(0, 4))
+ }
+ },
+ StartTime = 1000,
+ },
+ new BananaShower
+ {
+ StartTime = 5000,
+ Duration = 5000
+ }
+ }
+ };
+
+ private static void assertEffectivePositionsMirrored(IBeatmap original, IBeatmap mirrored)
+ {
+ if (original.HitObjects.Count != mirrored.HitObjects.Count)
+ Assert.Fail($"Top-level object count mismatch (original: {original.HitObjects.Count}, mirrored: {mirrored.HitObjects.Count})");
+
+ for (int i = 0; i < original.HitObjects.Count; ++i)
+ {
+ var originalObject = (CatchHitObject)original.HitObjects[i];
+ var mirroredObject = (CatchHitObject)mirrored.HitObjects[i];
+
+ // banana showers themselves are exempt, as we only really care about their nested bananas' positions.
+ if (!effectivePositionMirrored(originalObject, mirroredObject) && !(originalObject is BananaShower))
+ Assert.Fail($"{originalObject.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalObject, mirroredObject)})");
+
+ if (originalObject.NestedHitObjects.Count != mirroredObject.NestedHitObjects.Count)
+ Assert.Fail($"{originalObject.GetType().Name} nested object count mismatch (original: {originalObject.NestedHitObjects.Count}, mirrored: {mirroredObject.NestedHitObjects.Count})");
+
+ for (int j = 0; j < originalObject.NestedHitObjects.Count; ++j)
+ {
+ var originalNested = (CatchHitObject)originalObject.NestedHitObjects[j];
+ var mirroredNested = (CatchHitObject)mirroredObject.NestedHitObjects[j];
+
+ if (!effectivePositionMirrored(originalNested, mirroredNested))
+ Assert.Fail($"{originalObject.GetType().Name}'s nested {originalNested.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalNested, mirroredNested)})");
+ }
+ }
+ }
+
+ private static string printEffectivePositions(CatchHitObject original, CatchHitObject mirrored)
+ => $"original X: {original.EffectiveX}, mirrored X is: {mirrored.EffectiveX}, mirrored X should be: {CatchPlayfield.WIDTH - original.EffectiveX}";
+
+ private static bool effectivePositionMirrored(CatchHitObject original, CatchHitObject mirrored)
+ => Precision.AlmostEquals(original.EffectiveX, CatchPlayfield.WIDTH - mirrored.EffectiveX);
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index a3307c9224..6abfbdbe21 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
AddSliderStep("circle size", 0, 8, 5, createCatcher);
AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t)));
+ AddToggleStep("toggle hit lighting", lighting => config.SetValue(OsuSetting.HitLighting, lighting));
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
AddStep("catch many random fruit", () =>
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index eafa1b9b9d..9fee6b2bc1 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -117,6 +117,7 @@ namespace osu.Game.Rulesets.Catch
{
new CatchModDifficultyAdjust(),
new CatchModClassic(),
+ new CatchModMirror(),
};
case ModType.Automation:
diff --git a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
index e736d68740..371e901c69 100644
--- a/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
+++ b/osu.Game.Rulesets.Catch/CatchSkinComponents.cs
@@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Catch
Banana,
Droplet,
Catcher,
- CatchComboCounter
+ CatchComboCounter,
+ HitExplosion
}
}
diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
new file mode 100644
index 0000000000..932c8cad85
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
@@ -0,0 +1,87 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Mods
+{
+ public class CatchModMirror : ModMirror, IApplicableToBeatmap
+ {
+ public override string Description => "Fruits are flipped horizontally.";
+
+ ///
+ /// is used instead of ,
+ /// as applies offsets in .
+ /// runs after post-processing, while runs before it.
+ ///
+ public void ApplyToBeatmap(IBeatmap beatmap)
+ {
+ foreach (var hitObject in beatmap.HitObjects)
+ applyToHitObject(hitObject);
+ }
+
+ private void applyToHitObject(HitObject hitObject)
+ {
+ var catchObject = (CatchHitObject)hitObject;
+
+ switch (catchObject)
+ {
+ case Fruit fruit:
+ mirrorEffectiveX(fruit);
+ break;
+
+ case JuiceStream juiceStream:
+ mirrorEffectiveX(juiceStream);
+ mirrorJuiceStreamPath(juiceStream);
+ break;
+
+ case BananaShower bananaShower:
+ mirrorBananaShower(bananaShower);
+ break;
+ }
+ }
+
+ ///
+ /// Mirrors the effective X position of and its nested hit objects.
+ ///
+ private static void mirrorEffectiveX(CatchHitObject catchObject)
+ {
+ catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX;
+ catchObject.XOffset = -catchObject.XOffset;
+
+ foreach (var nested in catchObject.NestedHitObjects.Cast())
+ {
+ nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX;
+ nested.XOffset = -nested.XOffset;
+ }
+ }
+
+ ///
+ /// Mirrors the path of the .
+ ///
+ private static void mirrorJuiceStreamPath(JuiceStream juiceStream)
+ {
+ var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
+ foreach (var point in controlPoints)
+ point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y);
+
+ juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value);
+ }
+
+ ///
+ /// Mirrors X positions of all bananas in the .
+ ///
+ private static void mirrorBananaShower(BananaShower bananaShower)
+ {
+ foreach (var banana in bananaShower.NestedHitObjects.OfType())
+ banana.XOffset = CatchPlayfield.WIDTH - banana.XOffset;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
new file mode 100644
index 0000000000..e1fad564a3
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.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 osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Utils;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Default
+{
+ public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ private CircularContainer largeFaint;
+ private CircularContainer smallFaint;
+ private CircularContainer directionalGlow1;
+ private CircularContainer directionalGlow2;
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ Size = new Vector2(20);
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+
+ // scale roughly in-line with visual appearance of notes
+ const float initial_height = 10;
+
+ InternalChildren = new Drawable[]
+ {
+ largeFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ },
+ smallFaint = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Blending = BlendingParameters.Additive,
+ },
+ directionalGlow1 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ },
+ directionalGlow2 = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Size = new Vector2(0.01f, initial_height),
+ Blending = BlendingParameters.Additive,
+ }
+ };
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ X = entry.Position;
+ Scale = new Vector2(entry.HitObject.Scale);
+ setColour(entry.ObjectColour);
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ applyTransforms(entry.HitObject.RandomSeed);
+ }
+
+ private void applyTransforms(int randomSeed)
+ {
+ const double duration = 400;
+
+ // we want our size to be very small so the glow dominates it.
+ largeFaint.Size = new Vector2(0.8f);
+ largeFaint
+ .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
+ .FadeOut(duration * 2);
+
+ const float angle_variangle = 15; // should be less than 45
+ directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
+ directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
+
+ this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
+ }
+
+ private void setColour(Color4 objectColour)
+ {
+ const float roundness = 100;
+
+ largeFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
+ Roundness = 160,
+ Radius = 200,
+ };
+
+ smallFaint.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
+ Roundness = 20,
+ Radius = 50,
+ };
+
+ directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Glow,
+ Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
+ Roundness = roundness,
+ Radius = 40,
+ };
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
index 5e744ec001..10fc4e78b2 100644
--- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs
@@ -70,13 +70,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
if (version < 2.3m)
{
- if (GetTexture(@"fruit-ryuuta") != null ||
- GetTexture(@"fruit-ryuuta-0") != null)
+ if (hasOldStyleCatcherSprite())
return new LegacyCatcherOld();
}
- if (GetTexture(@"fruit-catcher-idle") != null ||
- GetTexture(@"fruit-catcher-idle-0") != null)
+ if (hasNewStyleCatcherSprite())
return new LegacyCatcherNew();
return null;
@@ -86,12 +84,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
return new LegacyCatchComboCounter(Skin);
return null;
+
+ case CatchSkinComponents.HitExplosion:
+ if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
+ return new LegacyHitExplosion();
+
+ return null;
}
}
return base.GetDrawableComponent(component);
}
+ private bool hasOldStyleCatcherSprite() =>
+ GetTexture(@"fruit-ryuuta") != null
+ || GetTexture(@"fruit-ryuuta-0") != null;
+
+ private bool hasNewStyleCatcherSprite() =>
+ GetTexture(@"fruit-catcher-idle") != null
+ || GetTexture(@"fruit-catcher-idle-0") != null;
+
public override IBindable GetConfig(TLookup lookup)
{
switch (lookup)
diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs
new file mode 100644
index 0000000000..c262b0a4ac
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyHitExplosion.cs
@@ -0,0 +1,94 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Skinning;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Skinning.Legacy
+{
+ public class LegacyHitExplosion : CompositeDrawable, IHitExplosion
+ {
+ [Resolved]
+ private Catcher catcher { get; set; }
+
+ private const float catch_margin = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2;
+
+ private readonly Sprite explosion1;
+ private readonly Sprite explosion2;
+
+ public LegacyHitExplosion()
+ {
+ Anchor = Anchor.BottomCentre;
+ Origin = Anchor.BottomCentre;
+ RelativeSizeAxes = Axes.Both;
+ Scale = new Vector2(0.5f);
+
+ InternalChildren = new[]
+ {
+ explosion1 = new Sprite
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.CentreLeft,
+ Alpha = 0,
+ Blending = BlendingParameters.Additive,
+ Rotation = -90
+ },
+ explosion2 = new Sprite
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.CentreLeft,
+ Alpha = 0,
+ Blending = BlendingParameters.Additive,
+ Rotation = -90
+ }
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(SkinManager skins)
+ {
+ var defaultLegacySkin = skins.DefaultLegacySkin;
+
+ // sprite names intentionally swapped to match stable member naming / ease of cross-referencing
+ explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
+ explosion2.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-1");
+ }
+
+ public void Animate(HitExplosionEntry entry)
+ {
+ Colour = entry.ObjectColour;
+
+ using (BeginAbsoluteSequence(entry.LifetimeStart))
+ {
+ float halfCatchWidth = catcher.CatchWidth / 2;
+ float explosionOffset = Math.Clamp(entry.Position, -halfCatchWidth + catch_margin * 3, halfCatchWidth - catch_margin * 3);
+
+ if (!(entry.HitObject is Droplet))
+ {
+ float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
+
+ explosion1.Scale = new Vector2(1, 0.9f);
+ explosion1.Position = new Vector2(explosionOffset, 0);
+
+ explosion1.FadeOutFromOne(300);
+ explosion1.ScaleTo(new Vector2(16 * scale, 1.1f), 160, Easing.Out);
+ }
+
+ explosion2.Scale = new Vector2(0.9f, 1);
+ explosion2.Position = new Vector2(explosionOffset, 0);
+
+ explosion2.FadeOutFromOne(700);
+ explosion2.ScaleTo(new Vector2(0.9f, 1.3f), 500, Easing.Out);
+
+ this.Delay(700).FadeOutFromOne();
+ }
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs
index 9fd4610e6e..5cd85aac56 100644
--- a/osu.Game.Rulesets.Catch/UI/Catcher.cs
+++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs
@@ -23,6 +23,7 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI
{
+ [Cached]
public class Catcher : SkinReloadableDrawable
{
///
@@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.UI
///
/// Width of the area that can be used to attempt catches during gameplay.
///
- private readonly float catchWidth;
+ public readonly float CatchWidth;
private readonly SkinnableCatcher body;
@@ -133,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (difficulty != null)
Scale = calculateScale(difficulty);
- catchWidth = CalculateCatchWidth(Scale);
+ CatchWidth = CalculateCatchWidth(Scale);
InternalChildren = new Drawable[]
{
@@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Catch.UI
if (!(hitObject is PalpableCatchHitObject fruit))
return false;
- float halfCatchWidth = catchWidth * 0.5f;
+ float halfCatchWidth = CatchWidth * 0.5f;
return fruit.EffectiveX >= X - halfCatchWidth &&
fruit.EffectiveX <= X + halfCatchWidth;
}
@@ -216,7 +217,7 @@ namespace osu.Game.Rulesets.Catch.UI
placeCaughtObject(palpableObject, positionInStack);
if (hitLighting.Value)
- addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
+ addLighting(result, drawableObject.AccentColour.Value, positionInStack.X);
}
// droplet doesn't affect the catcher state
@@ -365,8 +366,8 @@ namespace osu.Game.Rulesets.Catch.UI
return position;
}
- private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
- hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
+ private void addLighting(JudgementResult judgementResult, Color4 colour, float x) =>
+ hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, judgementResult, colour, x));
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
{
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
index d9ab428231..955b1e6edb 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosion.cs
@@ -1,129 +1,56 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Effects;
-using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Skinning.Default;
using osu.Game.Rulesets.Objects.Pooling;
-using osu.Game.Utils;
-using osuTK;
-using osuTK.Graphics;
+using osu.Game.Skinning;
+
+#nullable enable
namespace osu.Game.Rulesets.Catch.UI
{
public class HitExplosion : PoolableDrawableWithLifetime
{
- private readonly CircularContainer largeFaint;
- private readonly CircularContainer smallFaint;
- private readonly CircularContainer directionalGlow1;
- private readonly CircularContainer directionalGlow2;
+ private readonly SkinnableDrawable skinnableExplosion;
public HitExplosion()
{
- Size = new Vector2(20);
- Anchor = Anchor.TopCentre;
+ RelativeSizeAxes = Axes.Both;
+ Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
- // scale roughly in-line with visual appearance of notes
- const float initial_height = 10;
-
- InternalChildren = new Drawable[]
+ InternalChild = skinnableExplosion = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
{
- largeFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- },
- smallFaint = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Blending = BlendingParameters.Additive,
- },
- directionalGlow1 = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- },
- directionalGlow2 = new CircularContainer
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Masking = true,
- Size = new Vector2(0.01f, initial_height),
- Blending = BlendingParameters.Additive,
- }
+ CentreComponent = false,
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre
};
}
protected override void OnApply(HitExplosionEntry entry)
{
- X = entry.Position;
- Scale = new Vector2(entry.Scale);
- setColour(entry.ObjectColour);
-
- using (BeginAbsoluteSequence(entry.LifetimeStart))
- applyTransforms(entry.RNGSeed);
+ base.OnApply(entry);
+ if (IsLoaded)
+ apply(entry);
}
- private void applyTransforms(int randomSeed)
+ protected override void LoadComplete()
{
+ base.LoadComplete();
+ apply(Entry);
+ }
+
+ private void apply(HitExplosionEntry? entry)
+ {
+ if (entry == null)
+ return;
+
+ ApplyTransformsAt(double.MinValue, true);
ClearTransforms(true);
- const double duration = 400;
-
- // we want our size to be very small so the glow dominates it.
- largeFaint.Size = new Vector2(0.8f);
- largeFaint
- .ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
- .FadeOut(duration * 2);
-
- const float angle_variangle = 15; // should be less than 45
- directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
- directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
-
- this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
- }
-
- private void setColour(Color4 objectColour)
- {
- const float roundness = 100;
-
- largeFaint.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
- Roundness = 160,
- Radius = 200,
- };
-
- smallFaint.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
- Roundness = 20,
- Radius = 50,
- };
-
- directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Glow,
- Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
- Roundness = roundness,
- Radius = 40,
- };
+ (skinnableExplosion.Drawable as IHitExplosion)?.Animate(entry);
+ LifetimeEnd = skinnableExplosion.Drawable.LatestTransformEndTime;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
index 094d88243a..6df13e52ef 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosionContainer.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using osu.Framework.Graphics;
using osu.Framework.Graphics.Pooling;
using osu.Game.Rulesets.Objects.Pooling;
@@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
public HitExplosionContainer()
{
+ RelativeSizeAxes = Axes.Both;
+
AddInternal(pool = new DrawablePool(10));
}
diff --git a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
index b142962a8a..88871c77f6 100644
--- a/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
+++ b/osu.Game.Rulesets.Catch/UI/HitExplosionEntry.cs
@@ -2,24 +2,42 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics.Performance;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Judgements;
using osuTK.Graphics;
+#nullable enable
+
namespace osu.Game.Rulesets.Catch.UI
{
public class HitExplosionEntry : LifetimeEntry
{
- public readonly float Position;
- public readonly float Scale;
- public readonly Color4 ObjectColour;
- public readonly int RNGSeed;
+ ///
+ /// The judgement result that triggered this explosion.
+ ///
+ public JudgementResult JudgementResult { get; }
- public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
+ ///
+ /// The hitobject which triggered this explosion.
+ ///
+ public CatchHitObject HitObject => (CatchHitObject)JudgementResult.HitObject;
+
+ ///
+ /// The accent colour of the object caught.
+ ///
+ public Color4 ObjectColour { get; }
+
+ ///
+ /// The position at which the object was caught.
+ ///
+ public float Position { get; }
+
+ public HitExplosionEntry(double startTime, JudgementResult judgementResult, Color4 objectColour, float position)
{
LifetimeStart = startTime;
Position = position;
- Scale = scale;
+ JudgementResult = judgementResult;
ObjectColour = objectColour;
- RNGSeed = rngSeed;
}
}
}
diff --git a/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs b/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
new file mode 100644
index 0000000000..c744c00d9a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
@@ -0,0 +1,18 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+#nullable enable
+
+namespace osu.Game.Rulesets.Catch.UI
+{
+ ///
+ /// Common interface for all hit explosion skinnables.
+ ///
+ public interface IHitExplosion
+ {
+ ///
+ /// Begins animating this .
+ ///
+ void Animate(HitExplosionEntry entry);
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
index 0e61c02e2d..d4f1602a46 100644
--- a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
+++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditorRuleset.cs
@@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override GameplayCursorContainer CreateCursor() => null;
+ public OsuEditorPlayfield()
+ {
+ HitPolicy = new AnyOrderHitPolicy();
+ }
+
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
index f6fd3e36ab..587ff4b573 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorTrail.cs
@@ -9,6 +9,7 @@ using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Skinning;
+using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
@@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
private double lastTrailTime;
private IBindable cursorSize;
+ private Vector2? currentPosition;
+
public LegacyCursorTrail(ISkin skin)
{
this.skin = skin;
@@ -54,22 +57,34 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
}
protected override double FadeDuration => disjointTrail ? 150 : 500;
+ protected override float FadeExponent => 1;
protected override bool InterpolateMovements => !disjointTrail;
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
+ protected override void Update()
+ {
+ base.Update();
+
+ if (!disjointTrail || !currentPosition.HasValue)
+ return;
+
+ if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
+ {
+ lastTrailTime = Time.Current;
+ AddTrail(currentPosition.Value);
+ }
+ }
+
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (!disjointTrail)
return base.OnMouseMove(e);
- if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
- {
- lastTrailTime = Time.Current;
- return base.OnMouseMove(e);
- }
+ currentPosition = e.ScreenSpaceMousePosition;
+ // Intentionally block the base call as we're adding the trails ourselves.
return false;
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs
index 744ded37c9..1c8dfeac52 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBody.cs
@@ -3,9 +3,9 @@
using System;
using osu.Framework.Extensions.Color4Extensions;
-using osu.Framework.Utils;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Skinning.Default;
+using osu.Game.Utils;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// Roughly matches osu!stable's slider border portions.
=> base.CalculatedBorderPortion * 0.77f;
- public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
+ public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, 0.7f);
protected override Color4 ColourAt(float position)
{
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Color4 outerColour = AccentColour.Darken(0.1f);
Color4 innerColour = lighten(AccentColour, 0.5f);
- return Interpolation.ValueAt(position / realGradientPortion, outerColour, innerColour, 0, 1);
+ return LegacyUtils.InterpolateNonLinear(position / realGradientPortion, outerColour, innerColour, 0, 1);
}
///
diff --git a/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
new file mode 100644
index 0000000000..b4de91562b
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
@@ -0,0 +1,22 @@
+// 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.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Osu.UI
+{
+ ///
+ /// An which allows hitobjects to be hit in any order.
+ ///
+ public class AnyOrderHitPolicy : IHitPolicy
+ {
+ public IHitObjectContainer HitObjectContainer { get; set; }
+
+ public bool IsHittable(DrawableHitObject hitObject, double time) => true;
+
+ public void HandleHit(DrawableHitObject hitObject)
+ {
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
index 7f86e9daf7..7a95111c91 100644
--- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
+++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
@@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
private const int max_sprites = 2048;
+ ///
+ /// An exponentiating factor to ease the trail fade.
+ ///
+ protected virtual float FadeExponent => 1.7f;
+
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private int currentIndex;
private IShader shader;
@@ -141,22 +146,25 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override bool OnMouseMove(MouseMoveEvent e)
{
- Vector2 pos = e.ScreenSpaceMousePosition;
+ AddTrail(e.ScreenSpaceMousePosition);
+ return base.OnMouseMove(e);
+ }
- if (lastPosition == null)
+ protected void AddTrail(Vector2 position)
+ {
+ if (InterpolateMovements)
{
- lastPosition = pos;
- resampler.AddPosition(lastPosition.Value);
- return base.OnMouseMove(e);
- }
-
- foreach (Vector2 pos2 in resampler.AddPosition(pos))
- {
- Trace.Assert(lastPosition.HasValue);
-
- if (InterpolateMovements)
+ if (!lastPosition.HasValue)
{
- // ReSharper disable once PossibleInvalidOperationException
+ lastPosition = position;
+ resampler.AddPosition(lastPosition.Value);
+ return;
+ }
+
+ foreach (Vector2 pos2 in resampler.AddPosition(position))
+ {
+ Trace.Assert(lastPosition.HasValue);
+
Vector2 pos1 = lastPosition.Value;
Vector2 diff = pos2 - pos1;
float distance = diff.Length;
@@ -170,14 +178,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
addPart(lastPosition.Value);
}
}
- else
- {
- lastPosition = pos2;
- addPart(lastPosition.Value);
- }
}
-
- return base.OnMouseMove(e);
+ else
+ {
+ lastPosition = position;
+ addPart(lastPosition.Value);
+ }
}
private void addPart(Vector2 screenSpacePosition)
@@ -206,10 +212,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private Texture texture;
private float time;
+ private float fadeExponent;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 size;
-
private Vector2 originPosition;
private readonly QuadBatch vertexBatch = new QuadBatch(max_sprites, 1);
@@ -227,6 +233,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
texture = Source.texture;
size = Source.partSize;
time = Source.time;
+ fadeExponent = Source.FadeExponent;
originPosition = Vector2.Zero;
@@ -249,6 +256,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
shader.Bind();
shader.GetUniform("g_FadeClock").UpdateValue(ref time);
+ shader.GetUniform("g_FadeExponent").UpdateValue(ref fadeExponent);
texture.TextureGL.Bind();
diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
index 0ce71696bd..58f4c4c8db 100644
--- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs
@@ -2,11 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
+using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual;
namespace osu.Game.Tests.Gameplay
@@ -121,6 +123,18 @@ namespace osu.Game.Tests.Gameplay
AddAssert("Drawable lifetime is restored", () => dho.LifetimeStart == 666 && dho.LifetimeEnd == 999);
}
+ [Test]
+ public void TestStateChangeBeforeLoadComplete()
+ {
+ TestDrawableHitObject dho = null;
+ AddStep("Add DHO and apply result", () =>
+ {
+ Child = dho = new TestDrawableHitObject(new HitObject { StartTime = Time.Current });
+ dho.MissForcefully();
+ });
+ AddAssert("DHO state is correct", () => dho.State.Value == ArmedState.Miss);
+ }
+
private class TestDrawableHitObject : DrawableHitObject
{
public const double INITIAL_LIFETIME_OFFSET = 100;
@@ -141,6 +155,19 @@ namespace osu.Game.Tests.Gameplay
if (SetLifetimeStartOnApply)
LifetimeStart = LIFETIME_ON_APPLY;
}
+
+ public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
+
+ protected override void UpdateHitStateTransforms(ArmedState state)
+ {
+ if (state != ArmedState.Miss)
+ {
+ base.UpdateHitStateTransforms(state);
+ return;
+ }
+
+ this.FadeOut(1000);
+ }
}
private class TestLifetimeEntry : HitObjectLifetimeEntry
diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
index 0983b806e2..07ec86b0e7 100644
--- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
+++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs
@@ -24,6 +24,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddRepeatStep("add some users", () => Client.AddUser(new User { Id = id++ }), 5);
checkPlayingUserCount(0);
+ AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null);
+
changeState(3, MultiplayerUserState.WaitingForLoad);
checkPlayingUserCount(3);
@@ -41,6 +43,8 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("leave room", () => Client.LeaveRoom());
checkPlayingUserCount(0);
+
+ AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null);
}
[Test]
diff --git a/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs
index 2236f85b92..cc8503589d 100644
--- a/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs
+++ b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
+using osu.Framework.Testing;
using osu.Game.Graphics.Sprites;
using osu.Game.Online;
using osuTK;
@@ -15,6 +16,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Components
{
+ [HeadlessTest]
public class TestScenePollingComponent : OsuTestScene
{
private Container pollBox;
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
new file mode 100644
index 0000000000..299bbacf08
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs
@@ -0,0 +1,168 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.Rooms;
+using osu.Game.Online.Rooms.RoomStatuses;
+using osu.Game.Overlays;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Tests.Beatmaps;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneDrawableRoom : OsuTestScene
+ {
+ [Cached]
+ private readonly Bindable selectedRoom = new Bindable();
+
+ [Cached]
+ protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
+
+ [Test]
+ public void TestMultipleStatuses()
+ {
+ AddStep("create rooms", () =>
+ {
+ Child = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.9f),
+ Spacing = new Vector2(10),
+ Children = new Drawable[]
+ {
+ createDrawableRoom(new Room
+ {
+ Name = { Value = "Flyte's Trash Playlist" },
+ Status = { Value = new RoomStatusOpen() },
+ EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap =
+ {
+ Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
+ {
+ BeatmapInfo =
+ {
+ StarDifficulty = 2.5
+ }
+ }.BeatmapInfo,
+ }
+ }
+ }
+ }),
+ createDrawableRoom(new Room
+ {
+ Name = { Value = "Room 2" },
+ Status = { Value = new RoomStatusPlaying() },
+ EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
+ Playlist =
+ {
+ new PlaylistItem
+ {
+ Beatmap =
+ {
+ Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
+ {
+ BeatmapInfo =
+ {
+ StarDifficulty = 2.5
+ }
+ }.BeatmapInfo,
+ }
+ },
+ new PlaylistItem
+ {
+ Beatmap =
+ {
+ Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
+ {
+ BeatmapInfo =
+ {
+ StarDifficulty = 4.5
+ }
+ }.BeatmapInfo,
+ }
+ }
+ }
+ }),
+ createDrawableRoom(new Room
+ {
+ Name = { Value = "Room 3" },
+ Status = { Value = new RoomStatusEnded() },
+ EndDate = { Value = DateTimeOffset.Now },
+ }),
+ createDrawableRoom(new Room
+ {
+ Name = { Value = "Room 4 (realtime)" },
+ Status = { Value = new RoomStatusOpen() },
+ Category = { Value = RoomCategory.Realtime },
+ }),
+ createDrawableRoom(new Room
+ {
+ Name = { Value = "Room 4 (spotlight)" },
+ Status = { Value = new RoomStatusOpen() },
+ Category = { Value = RoomCategory.Spotlight },
+ }),
+ }
+ };
+ });
+ }
+
+ [Test]
+ public void TestEnableAndDisablePassword()
+ {
+ DrawableRoom drawableRoom = null;
+ Room room = null;
+
+ AddStep("create room", () => Child = drawableRoom = createDrawableRoom(room = new Room
+ {
+ Name = { Value = "Room with password" },
+ Status = { Value = new RoomStatusOpen() },
+ Category = { Value = RoomCategory.Realtime },
+ }));
+
+ AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha));
+
+ AddStep("set password", () => room.Password.Value = "password");
+ AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType().Single().Alpha));
+
+ AddStep("unset password", () => room.Password.Value = string.Empty);
+ AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha));
+ }
+
+ private DrawableRoom createDrawableRoom(Room room)
+ {
+ room.Host.Value ??= new User { Username = "peppy", Id = 2 };
+
+ if (room.RecentParticipants.Count == 0)
+ {
+ room.RecentParticipants.AddRange(Enumerable.Range(0, 20).Select(i => new User
+ {
+ Id = i,
+ Username = $"User {i}"
+ }));
+ }
+
+ var drawableRoom = new DrawableRoom(room) { MatchingFilter = true };
+ drawableRoom.Action = () => drawableRoom.State = drawableRoom.State == SelectionState.Selected ? SelectionState.NotSelected : SelectionState.Selected;
+
+ return drawableRoom;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
index dfb78a235b..93bdbb79f4 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs
@@ -14,7 +14,7 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Rooms;
-using osu.Game.Overlays;
+using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertDownloadButtonVisible(false);
void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}",
- () => playlist.ChildrenOfType().Single().Alpha == (visible ? 1 : 0));
+ () => playlist.ChildrenOfType().Single().Alpha == (visible ? 1 : 0));
}
[Test]
@@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
createPlaylist(byOnlineId, byChecksum);
- AddAssert("download buttons shown", () => playlist.ChildrenOfType().All(d => d.IsPresent));
+ AddAssert("download buttons shown", () => playlist.ChildrenOfType().All(d => d.IsPresent));
}
[Test]
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
deleted file mode 100644
index 471d0b6c98..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs
+++ /dev/null
@@ -1,49 +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 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 : OnlinePlayTestScene
- {
- [SetUp]
- public new void Setup() => Schedule(() =>
- {
- SelectedRoom.Value = new Room();
-
- Child = new RoomInfo
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- Width = 500
- };
- });
-
- [Test]
- public void TestNonSelectedRoom()
- {
- AddStep("set null room", () => SelectedRoom.Value.RoomID.Value = null);
- }
-
- [Test]
- public void TestOpenRoom()
- {
- AddStep("set open room", () =>
- {
- 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/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
index e14df62af1..ade24b8740 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs
@@ -6,9 +6,11 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Timing;
+using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
using osu.Game.Screens.Play.HUD;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -31,7 +33,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
};
foreach (var (userId, _) in clocks)
+ {
SpectatorClient.StartPlay(userId, 0);
+ OnlinePlayDependencies.Client.AddUser(new User { Id = userId });
+ }
});
AddStep("create leaderboard", () =>
@@ -41,7 +46,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
var scoreProcessor = new OsuScoreProcessor();
scoreProcessor.ApplyBeatmap(playable);
- LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.ToArray()) { Expanded = { Value = true } }, Add);
+ LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()) { Expanded = { Value = true } }, Add);
});
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
index 072e32370d..65b1d6d53a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -8,10 +8,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
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.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -25,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiSpectatorScreen spectatorScreen;
- private readonly List playingUserIds = new List();
+ private readonly List playingUsers = new List();
private BeatmapSetInfo importedSet;
private BeatmapInfo importedBeatmap;
@@ -40,17 +43,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[SetUp]
- public new void Setup() => Schedule(() => playingUserIds.Clear());
+ public new void Setup() => Schedule(() => playingUsers.Clear());
[Test]
public void TestDelayedStart()
{
AddStep("start players silently", () =>
{
- Client.CurrentMatchPlayingUserIds.Add(PLAYER_1_ID);
- Client.CurrentMatchPlayingUserIds.Add(PLAYER_2_ID);
- playingUserIds.Add(PLAYER_1_ID);
- playingUserIds.Add(PLAYER_2_ID);
+ OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
+ OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
+
+ playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
+ playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
});
loadSpectateScreen(false);
@@ -76,6 +80,38 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddWaitStep("wait a bit", 20);
}
+ [Test]
+ public void TestTeamDisplay()
+ {
+ AddStep("start players", () =>
+ {
+ var player1 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
+ player1.MatchState = new TeamVersusUserState
+ {
+ TeamID = 0,
+ };
+
+ var player2 = OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
+ player2.MatchState = new TeamVersusUserState
+ {
+ TeamID = 1,
+ };
+
+ SpectatorClient.StartPlay(player1.UserID, importedBeatmapId);
+ SpectatorClient.StartPlay(player2.UserID, importedBeatmapId);
+
+ playingUsers.Add(player1);
+ playingUsers.Add(player2);
+ });
+
+ loadSpectateScreen();
+
+ sendFrames(PLAYER_1_ID, 1000);
+ sendFrames(PLAYER_2_ID, 1000);
+
+ AddWaitStep("wait a bit", 20);
+ }
+
[Test]
public void TestTimeDoesNotProgressWhileAllPlayersPaused()
{
@@ -252,7 +288,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Beatmap.Value = beatmapManager.GetWorkingBeatmap(importedBeatmap);
Ruleset.Value = importedBeatmap.Ruleset;
- LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUserIds.ToArray()));
+ LoadScreen(spectatorScreen = new MultiSpectatorScreen(playingUsers.ToArray()));
});
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
@@ -264,9 +300,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
foreach (int id in userIds)
{
- Client.CurrentMatchPlayingUserIds.Add(id);
+ OnlinePlayDependencies.Client.AddUser(new User { Id = id }, true);
+
SpectatorClient.StartPlay(id, beatmapId ?? importedBeatmapId);
- playingUserIds.Add(id);
+ playingUsers.Add(new MultiplayerRoomUser(id));
}
});
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index cd3c50cf14..08b3fb98a8 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -25,7 +25,7 @@ using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
-using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Resources;
@@ -87,6 +87,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestEmpty()
{
// used to test the flow of multiplayer from visual tests.
+ AddStep("empty step", () => { });
}
[Test]
@@ -312,6 +313,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
@@ -348,6 +351,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddStep("restore beatmap", () =>
@@ -396,7 +401,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
- AddStep("open mod overlay", () => this.ChildrenOfType().ElementAt(2).TriggerClick());
+ AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick());
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
@@ -404,8 +409,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
- testLeave("lounge tab item", () => this.ChildrenOfType.BreadcrumbTabItem>().First().TriggerClick());
-
testLeave("back button", () => multiplayerScreen.OnBackButton());
// mimics home button and OS window close
@@ -423,10 +426,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createRoom(Func room)
{
- AddStep("open room", () =>
- {
- multiplayerScreen.OpenNewRoom(room());
- });
+ AddUntilStep("wait for lounge", () => multiplayerScreen.ChildrenOfType().SingleOrDefault()?.IsLoaded == true);
+ AddStep("open room", () => multiplayerScreen.ChildrenOfType().Single().Open(room()));
AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
index 0e368b59dd..3317ddc767 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs
@@ -12,6 +12,7 @@ using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Online.API;
+using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Replays.Legacy;
using osu.Game.Rulesets.Osu.Scoring;
@@ -20,6 +21,7 @@ using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Visual.OnlinePlay;
using osu.Game.Tests.Visual.Spectator;
+using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -50,22 +52,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
OsuScoreProcessor scoreProcessor;
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
- var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
+ var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
+ var multiplayerUsers = new List();
foreach (var user in users)
+ {
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
-
- // Todo: This is REALLY bad.
- Client.CurrentMatchPlayingUserIds.AddRange(users);
+ multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true));
+ }
Children = new Drawable[]
{
scoreProcessor = new OsuScoreProcessor(),
};
- scoreProcessor.ApplyBeatmap(playable);
+ scoreProcessor.ApplyBeatmap(playableBeatmap);
- LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, users.ToArray())
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs
new file mode 100644
index 0000000000..dfaf2f1dc3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.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.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Framework.Utils;
+using osu.Game.Online.API;
+using osu.Game.Online.Multiplayer;
+using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets.Osu.Scoring;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Screens.Play.HUD;
+using osu.Game.Tests.Visual.OnlinePlay;
+using osu.Game.Tests.Visual.Spectator;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerGameplayLeaderboardTeams : MultiplayerTestScene
+ {
+ private static IEnumerable users => Enumerable.Range(0, 16);
+
+ public new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient SpectatorClient =>
+ (TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient)OnlinePlayDependencies?.SpectatorClient;
+
+ protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
+
+ protected class TestDependencies : MultiplayerTestSceneDependencies
+ {
+ protected override TestSpectatorClient CreateSpectatorClient() => new TestSceneMultiplayerGameplayLeaderboard.TestMultiplayerSpectatorClient();
+ }
+
+ private MultiplayerGameplayLeaderboard leaderboard;
+ private GameplayMatchScoreDisplay gameplayScoreDisplay;
+
+ protected override Room CreateRoom()
+ {
+ var room = base.CreateRoom();
+ room.Type.Value = MatchType.TeamVersus;
+ return room;
+ }
+
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result);
+
+ AddStep("create leaderboard", () =>
+ {
+ leaderboard?.Expire();
+
+ OsuScoreProcessor scoreProcessor;
+ Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
+
+ var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
+ var multiplayerUsers = new List();
+
+ foreach (var user in users)
+ {
+ SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0);
+ var roomUser = OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
+
+ roomUser.MatchState = new TeamVersusUserState
+ {
+ TeamID = RNG.Next(0, 2)
+ };
+
+ multiplayerUsers.Add(roomUser);
+ }
+
+ Children = new Drawable[]
+ {
+ scoreProcessor = new OsuScoreProcessor(),
+ };
+
+ scoreProcessor.ApplyBeatmap(playableBeatmap);
+
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(scoreProcessor, multiplayerUsers.ToArray())
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }, gameplayLeaderboard =>
+ {
+ LoadComponentAsync(new MatchScoreDisplay
+ {
+ Team1Score = { BindTarget = leaderboard.TeamScores[0] },
+ Team2Score = { BindTarget = leaderboard.TeamScores[1] }
+ }, Add);
+
+ LoadComponentAsync(gameplayScoreDisplay = new GameplayMatchScoreDisplay
+ {
+ Anchor = Anchor.BottomCentre,
+ Origin = Anchor.BottomCentre,
+ Team1Score = { BindTarget = leaderboard.TeamScores[0] },
+ Team2Score = { BindTarget = leaderboard.TeamScores[1] }
+ }, Add);
+
+ Add(gameplayLeaderboard);
+ });
+ });
+
+ AddUntilStep("wait for load", () => leaderboard.IsLoaded);
+ AddUntilStep("wait for user population", () => Client.CurrentMatchPlayingUserIds.Count > 0);
+ }
+
+ [Test]
+ public void TestScoreUpdates()
+ {
+ AddRepeatStep("update state", () => SpectatorClient.RandomlyUpdateState(), 100);
+ AddToggleStep("switch compact mode", expanded =>
+ {
+ leaderboard.Expanded.Value = expanded;
+ gameplayScoreDisplay.Expanded.Value = expanded;
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 955be6ca21..ea10fc1b8b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -129,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Enabled.Value);
AddStep("click ready button", () =>
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
index 6526f7eea7..a3e6c8de3b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
@@ -155,6 +155,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("second user crown visible", () => this.ChildrenOfType().ElementAt(1).ChildrenOfType().First().Alpha == 1);
}
+ [Test]
+ public void TestKickButtonOnlyPresentWhenHost()
+ {
+ AddStep("add user", () => Client.AddUser(new User
+ {
+ Id = 3,
+ Username = "Second",
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ }));
+
+ AddUntilStep("kick buttons visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 1);
+
+ AddStep("make second user host", () => Client.TransferHost(3));
+
+ AddUntilStep("kick buttons not visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 0);
+
+ AddStep("make local user host again", () => Client.TransferHost(API.LocalUser.Value.Id));
+
+ AddUntilStep("kick buttons visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 1);
+ }
+
+ [Test]
+ public void TestKickButtonKicks()
+ {
+ AddStep("add user", () => Client.AddUser(new User
+ {
+ Id = 3,
+ Username = "Second",
+ CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
+ }));
+
+ AddStep("kick second user", () => this.ChildrenOfType().Single(d => d.IsPresent).TriggerClick());
+
+ AddAssert("second user kicked", () => Client.Room?.Users.Single().UserID == API.LocalUser.Value.Id);
+ }
+
[Test]
public void TestManyUsers()
{
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
new file mode 100644
index 0000000000..9e03743e8d
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRankRangePill.cs
@@ -0,0 +1,95 @@
+// 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.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneRankRangePill : MultiplayerTestScene
+ {
+ [SetUp]
+ public new void Setup() => Schedule(() =>
+ {
+ Child = new RankRangePill
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre
+ };
+ });
+
+ [Test]
+ public void TestSingleUser()
+ {
+ AddStep("add user", () =>
+ {
+ Client.AddUser(new User
+ {
+ Id = 2,
+ Statistics = { GlobalRank = 1234 }
+ });
+
+ // Remove the local user so only the one above is displayed.
+ Client.RemoveUser(API.LocalUser.Value);
+ });
+ }
+
+ [Test]
+ public void TestMultipleUsers()
+ {
+ AddStep("add users", () =>
+ {
+ Client.AddUser(new User
+ {
+ Id = 2,
+ Statistics = { GlobalRank = 1234 }
+ });
+
+ Client.AddUser(new User
+ {
+ Id = 3,
+ Statistics = { GlobalRank = 3333 }
+ });
+
+ Client.AddUser(new User
+ {
+ Id = 4,
+ Statistics = { GlobalRank = 4321 }
+ });
+
+ // Remove the local user so only the ones above are displayed.
+ Client.RemoveUser(API.LocalUser.Value);
+ });
+ }
+
+ [TestCase(1, 10)]
+ [TestCase(10, 100)]
+ [TestCase(100, 1000)]
+ [TestCase(1000, 10000)]
+ [TestCase(10000, 100000)]
+ [TestCase(100000, 1000000)]
+ [TestCase(1000000, 10000000)]
+ public void TestRange(int min, int max)
+ {
+ AddStep("add users", () =>
+ {
+ Client.AddUser(new User
+ {
+ Id = 2,
+ Statistics = { GlobalRank = min }
+ });
+
+ Client.AddUser(new User
+ {
+ Id = 3,
+ Statistics = { GlobalRank = max }
+ });
+
+ // Remove the local user so only the ones above are displayed.
+ Client.RemoveUser(API.LocalUser.Value);
+ });
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRecentParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRecentParticipantsList.cs
new file mode 100644
index 0000000000..50ec2bf3ac
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRecentParticipantsList.cs
@@ -0,0 +1,143 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Testing;
+using osu.Game.Online.Rooms;
+using osu.Game.Screens.OnlinePlay.Lounge.Components;
+using osu.Game.Tests.Visual.OnlinePlay;
+using osu.Game.Users;
+using osu.Game.Users.Drawables;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneRecentParticipantsList : OnlinePlayTestScene
+ {
+ private RecentParticipantsList list;
+
+ [SetUp]
+ public new void Setup() => Schedule(() =>
+ {
+ SelectedRoom.Value = new Room { Name = { Value = "test room" } };
+
+ Child = list = new RecentParticipantsList
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ NumberOfCircles = 4
+ };
+ });
+
+ [Test]
+ public void TestCircleCountNearLimit()
+ {
+ AddStep("add 8 users", () =>
+ {
+ for (int i = 0; i < 8; i++)
+ addUser(i);
+ });
+
+ AddStep("set 8 circles", () => list.NumberOfCircles = 8);
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+
+ AddStep("add one more user", () => addUser(9));
+ AddAssert("2 hidden users", () => list.ChildrenOfType().Single().Count == 2);
+
+ AddStep("remove first user", () => removeUserAt(0));
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+
+ AddStep("add one more user", () => addUser(9));
+ AddAssert("2 hidden users", () => list.ChildrenOfType().Single().Count == 2);
+
+ AddStep("remove last user", () => removeUserAt(8));
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+ }
+
+ [Test]
+ public void TestHiddenUsersBecomeDisplayed()
+ {
+ AddStep("add 8 users", () =>
+ {
+ for (int i = 0; i < 8; i++)
+ addUser(i);
+ });
+
+ AddStep("set 3 circles", () => list.NumberOfCircles = 3);
+
+ for (int i = 0; i < 8; i++)
+ {
+ AddStep("remove user", () => removeUserAt(0));
+ int remainingUsers = 7 - i;
+
+ int displayedUsers = remainingUsers > 3 ? 2 : remainingUsers;
+ AddAssert($"{displayedUsers} avatars displayed", () => list.ChildrenOfType().Count() == displayedUsers);
+ }
+ }
+
+ [Test]
+ public void TestCircleCount()
+ {
+ AddStep("add 50 users", () =>
+ {
+ for (int i = 0; i < 50; i++)
+ addUser(i);
+ });
+
+ AddStep("set 3 circles", () => list.NumberOfCircles = 3);
+ AddAssert("2 users displayed", () => list.ChildrenOfType().Count() == 2);
+ AddAssert("48 hidden users", () => list.ChildrenOfType().Single().Count == 48);
+
+ AddStep("set 10 circles", () => list.NumberOfCircles = 10);
+ AddAssert("9 users displayed", () => list.ChildrenOfType().Count() == 9);
+ AddAssert("41 hidden users", () => list.ChildrenOfType().Single().Count == 41);
+ }
+
+ [Test]
+ public void TestAddAndRemoveUsers()
+ {
+ AddStep("add 50 users", () =>
+ {
+ for (int i = 0; i < 50; i++)
+ addUser(i);
+ });
+
+ AddStep("remove from start", () => removeUserAt(0));
+ AddAssert("3 circles displayed", () => list.ChildrenOfType().Count() == 3);
+ AddAssert("46 hidden users", () => list.ChildrenOfType().Single().Count == 46);
+
+ AddStep("remove from end", () => removeUserAt(SelectedRoom.Value.RecentParticipants.Count - 1));
+ AddAssert("3 circles displayed", () => list.ChildrenOfType().Count() == 3);
+ AddAssert("45 hidden users", () => list.ChildrenOfType().Single().Count == 45);
+
+ AddRepeatStep("remove 45 users", () => removeUserAt(0), 45);
+ AddAssert("3 circles displayed", () => list.ChildrenOfType().Count() == 3);
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+ AddAssert("hidden users bubble hidden", () => list.ChildrenOfType().Single().Alpha < 0.5f);
+
+ AddStep("remove another user", () => removeUserAt(0));
+ AddAssert("2 circles displayed", () => list.ChildrenOfType().Count() == 2);
+ AddAssert("0 hidden users", () => list.ChildrenOfType().Single().Count == 0);
+
+ AddRepeatStep("remove the remaining two users", () => removeUserAt(0), 2);
+ AddAssert("0 circles displayed", () => !list.ChildrenOfType().Any());
+ }
+
+ private void addUser(int id)
+ {
+ SelectedRoom.Value.RecentParticipants.Add(new User
+ {
+ Id = id,
+ Username = $"User {id}"
+ });
+ SelectedRoom.Value.ParticipantCount.Value++;
+ }
+
+ private void removeUserAt(int index)
+ {
+ SelectedRoom.Value.RecentParticipants.RemoveAt(index);
+ SelectedRoom.Value.ParticipantCount.Value--;
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs
deleted file mode 100644
index 8c4133418c..0000000000
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs
+++ /dev/null
@@ -1,81 +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 System.Linq;
-using NUnit.Framework;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Testing;
-using osu.Framework.Utils;
-using osu.Game.Online.Rooms;
-using osu.Game.Online.Rooms.RoomStatuses;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
-
-namespace osu.Game.Tests.Visual.Multiplayer
-{
- public class TestSceneRoomStatus : OsuTestScene
- {
- [Test]
- public void TestMultipleStatuses()
- {
- AddStep("create rooms", () =>
- {
- Child = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.Both,
- Width = 0.5f,
- Children = new Drawable[]
- {
- new DrawableRoom(new Room
- {
- Name = { Value = "Open - ending in 1 day" },
- Status = { Value = new RoomStatusOpen() },
- EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
- }) { MatchingFilter = true },
- new DrawableRoom(new Room
- {
- Name = { Value = "Playing - ending in 1 day" },
- Status = { Value = new RoomStatusPlaying() },
- EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
- }) { MatchingFilter = true },
- new DrawableRoom(new Room
- {
- Name = { Value = "Ended" },
- Status = { Value = new RoomStatusEnded() },
- EndDate = { Value = DateTimeOffset.Now }
- }) { MatchingFilter = true },
- new DrawableRoom(new Room
- {
- Name = { Value = "Open" },
- Status = { Value = new RoomStatusOpen() },
- Category = { Value = RoomCategory.Realtime }
- }) { MatchingFilter = true },
- }
- };
- });
- }
-
- [Test]
- public void TestEnableAndDisablePassword()
- {
- DrawableRoom drawableRoom = null;
- Room room = null;
-
- AddStep("create room", () => Child = drawableRoom = new DrawableRoom(room = new Room
- {
- Name = { Value = "Room with password" },
- Status = { Value = new RoomStatusOpen() },
- Category = { Value = RoomCategory.Realtime },
- }) { MatchingFilter = true });
-
- AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha));
-
- AddStep("set password", () => room.Password.Value = "password");
- AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType().Single().Alpha));
-
- AddStep("unset password", () => room.Password.Value = string.Empty);
- AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha));
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
index e19665497d..a8fda19c60 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs
@@ -17,6 +17,7 @@ using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer.Participants;
@@ -150,10 +151,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createRoom(Func room)
{
- AddStep("open room", () =>
- {
- multiplayerScreen.OpenNewRoom(room());
- });
+ AddStep("open room", () => multiplayerScreen.ChildrenOfType().Single().Open(room()));
AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true);
AddWaitStep("wait for transition", 2);
diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
similarity index 98%
rename from osu.Game.Tests/Visual/TestSceneOsuGame.cs
rename to osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
index c52d846a68..26641214b1 100644
--- a/osu.Game.Tests/Visual/TestSceneOsuGame.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs
@@ -10,7 +10,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
-using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
@@ -30,10 +29,9 @@ using osu.Game.Skinning;
using osu.Game.Utils;
using osuTK.Graphics;
-namespace osu.Game.Tests.Visual
+namespace osu.Game.Tests.Visual.Navigation
{
[TestFixture]
- [HeadlessTest]
public class TestSceneOsuGame : OsuTestScene
{
private IReadOnlyList requiredGameDependencies => new[]
diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
index 7188a4e57f..3c65f46c79 100644
--- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
+++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs
@@ -16,6 +16,7 @@ using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components;
+using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking;
using osu.Game.Screens.Select;
@@ -316,7 +317,8 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => multiplayer = new TestMultiplayer());
- AddStep("open room", () => multiplayer.OpenNewRoom());
+ AddUntilStep("wait for lounge", () => multiplayer.ChildrenOfType().SingleOrDefault()?.IsLoaded == true);
+ AddStep("open room", () => multiplayer.ChildrenOfType().Single().Open());
AddStep("press back button", () => Game.ChildrenOfType().First().Action());
AddWaitStep("wait two frames", 2);
}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
index 8818ac75b1..8f000afb91 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using Humanizer;
using NUnit.Framework;
+using osu.Framework.Testing;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@@ -95,9 +96,11 @@ namespace osu.Game.Tests.Visual.Online
AddAssert(@"no stream selected", () => changelog.Header.Streams.Current.Value == null);
}
- [Test]
- public void ShowWithBuild()
+ [TestCase(false)]
+ [TestCase(true)]
+ public void ShowWithBuild(bool isSupporter)
{
+ AddStep(@"set supporter", () => dummyAPI.LocalUser.Value.IsSupporter = isSupporter);
showBuild(() => new APIChangelogBuild
{
Version = "2018.712.0",
@@ -155,6 +158,8 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep(@"wait for streams", () => changelog.Streams?.Count > 0);
AddAssert(@"correct build displayed", () => changelog.Current.Value.Version == "2018.712.0");
AddAssert(@"correct stream selected", () => changelog.Header.Streams.Current.Value.Id == 5);
+ AddUntilStep(@"wait for content load", () => changelog.ChildrenOfType().Any());
+ AddAssert(@"supporter promo showed", () => changelog.ChildrenOfType().First().Alpha == (isSupporter ? 0 : 1));
}
[Test]
diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogSupporterPromo.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogSupporterPromo.cs
new file mode 100644
index 0000000000..22220a7d9c
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogSupporterPromo.cs
@@ -0,0 +1,35 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Game.Overlays;
+using osu.Game.Overlays.Changelog;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ public class TestSceneChangelogSupporterPromo : OsuTestScene
+ {
+ [Cached]
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+
+ public TestSceneChangelogSupporterPromo()
+ {
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background4,
+ },
+ new ChangelogSupporterPromo(),
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs b/osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
similarity index 70%
rename from osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs
rename to osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
index 7fdf0708e0..628ae0971b 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
@@ -3,84 +3,52 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
using osu.Game.Overlays.Comments;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
-using osu.Game.Graphics.UserInterface;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics;
-using osuTK;
using JetBrains.Annotations;
-using NUnit.Framework;
+using osu.Framework.Testing;
namespace osu.Game.Tests.Visual.Online
{
- public class TestSceneCommentsPage : OsuTestScene
+ public class TestSceneOfflineCommentsContainer : OsuTestScene
{
[Cached]
- private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
- private readonly BindableBool showDeleted = new BindableBool();
- private readonly Container content;
+ private TestCommentsContainer comments;
- private TestCommentsPage commentsPage;
-
- public TestSceneCommentsPage()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Add(new FillFlowContainer
+ Clear();
+ Add(new BasicScrollContainer
{
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 10),
- Children = new Drawable[]
- {
- new Container
- {
- AutoSizeAxes = Axes.Y,
- Width = 200,
- Child = new OsuCheckbox
- {
- Current = showDeleted,
- LabelText = @"Show Deleted"
- }
- },
- content = new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- }
- }
+ RelativeSizeAxes = Axes.Both,
+ Child = comments = new TestCommentsContainer()
});
- }
+ });
[Test]
public void TestAppendDuplicatedComment()
{
- AddStep("Create page", () => createPage(getCommentBundle()));
- AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
- AddStep("Append existing comment", () => commentsPage?.AppendComments(getCommentSubBundle()));
- AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
+ AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
+ AddUntilStep("Dictionary length is 10", () => comments.DictionaryLength == 10);
+ AddStep("Append existing comment", () => comments.AppendComments(getCommentSubBundle()));
+ AddAssert("Dictionary length is 10", () => comments.DictionaryLength == 10);
}
[Test]
- public void TestEmptyBundle()
+ public void TestLocalCommentBundle()
{
- AddStep("Create page", () => createPage(getEmptyCommentBundle()));
- AddAssert("Dictionary length is 0", () => commentsPage?.DictionaryLength == 0);
- }
-
- private void createPage(CommentBundle commentBundle)
- {
- commentsPage = null;
- content.Clear();
- content.Add(commentsPage = new TestCommentsPage(commentBundle)
- {
- ShowDeleted = { BindTarget = showDeleted }
- });
+ AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
+ AddStep("Add empty comment bundle", () => comments.ShowComments(getEmptyCommentBundle()));
}
private CommentBundle getEmptyCommentBundle() => new CommentBundle
@@ -193,6 +161,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "Good_Admin"
}
},
+ Total = 10
};
private CommentBundle getCommentSubBundle() => new CommentBundle
@@ -211,16 +180,18 @@ namespace osu.Game.Tests.Visual.Online
IncludedComments = new List(),
};
- private class TestCommentsPage : CommentsPage
+ private class TestCommentsContainer : CommentsContainer
{
- public TestCommentsPage(CommentBundle commentBundle)
- : base(commentBundle)
- {
- }
-
public new void AppendComments([NotNull] CommentBundle bundle) => base.AppendComments(bundle);
public int DictionaryLength => CommentDictionary.Count;
+
+ public void ShowComments(CommentBundle bundle)
+ {
+ this.ChildrenOfType().Single().Current.Value = 0;
+ ClearComments();
+ OnSuccess(bundle);
+ }
}
}
}
diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsFilterControl.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsFilterControl.cs
deleted file mode 100644
index 40e191dd7e..0000000000
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsFilterControl.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using osu.Framework.Graphics;
-using osu.Game.Screens.OnlinePlay.Lounge.Components;
-
-namespace osu.Game.Tests.Visual.Playlists
-{
- public class TestScenePlaylistsFilterControl : OsuTestScene
- {
- public TestScenePlaylistsFilterControl()
- {
- Child = new PlaylistsFilterControl
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.X,
- Width = 0.7f,
- Height = 80,
- };
- }
- }
-}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs
index 53693d1b70..3b43f8485a 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.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.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -68,13 +70,40 @@ namespace osu.Game.Tests.Visual.UserInterface
);
}
- private class MyContextMenuContainer : Container, IHasContextMenu
+ private static MenuItem[] makeMenu()
{
- public MenuItem[] ContextMenuItems => new MenuItem[]
+ return new MenuItem[]
{
new OsuMenuItem(@"Some option"),
new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted),
new OsuMenuItem(@"Another option"),
+ new OsuMenuItem(@"Nested option >")
+ {
+ Items = new MenuItem[]
+ {
+ new OsuMenuItem(@"Sub-One"),
+ new OsuMenuItem(@"Sub-Two"),
+ new OsuMenuItem(@"Sub-Three"),
+ new OsuMenuItem(@"Sub-Nested option >")
+ {
+ Items = new MenuItem[]
+ {
+ new OsuMenuItem(@"Double Sub-One"),
+ new OsuMenuItem(@"Double Sub-Two"),
+ new OsuMenuItem(@"Double Sub-Three"),
+ new OsuMenuItem(@"Sub-Sub-Nested option >")
+ {
+ Items = new MenuItem[]
+ {
+ new OsuMenuItem(@"Too Deep One"),
+ new OsuMenuItem(@"Too Deep Two"),
+ new OsuMenuItem(@"Too Deep Three"),
+ }
+ }
+ }
+ }
+ }
+ },
new OsuMenuItem(@"Choose me please"),
new OsuMenuItem(@"And me too"),
new OsuMenuItem(@"Trying to fill"),
@@ -82,17 +111,29 @@ namespace osu.Game.Tests.Visual.UserInterface
};
}
+ private class MyContextMenuContainer : Container, IHasContextMenu
+ {
+ public MenuItem[] ContextMenuItems => makeMenu();
+ }
+
private class AnotherContextMenuContainer : Container, IHasContextMenu
{
- public MenuItem[] ContextMenuItems => new MenuItem[]
+ public MenuItem[] ContextMenuItems
{
- new OsuMenuItem(@"Simple option"),
- new OsuMenuItem(@"Simple very very long option"),
- new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
- new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
- new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
- new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
- };
+ get
+ {
+ List
-
-
+
+
@@ -93,7 +93,7 @@
-
+