diff --git a/osu.Android.props b/osu.Android.props
index 454bb46059..ec223f98c2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
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/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/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/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
index 636cd63c69..3102db270e 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
+using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
@@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset)
{
- drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
+ drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield, drawableRuleset.Beatmap));
}
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
@@ -128,8 +129,21 @@ namespace osu.Game.Rulesets.Osu.Mods
protected override void Update()
{
- float start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
- float end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
+ float start, end;
+
+ if (Precision.AlmostEquals(restrictTo.Rotation, 0))
+ {
+ start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
+ end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
+ }
+ else
+ {
+ float center = restrictTo.ToSpaceOfOtherDrawable(restrictTo.OriginPosition, Parent).X;
+ float halfDiagonal = (restrictTo.DrawSize / 2).LengthFast;
+
+ start = center - halfDiagonal;
+ end = center + halfDiagonal;
+ }
float rawWidth = end - start;
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/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/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
index 059432eeaf..855a75117d 100644
--- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
+++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapEncoderTest.cs
@@ -169,7 +169,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
- protected override ISkin GetSkin() => throw new NotImplementedException();
+ protected internal override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
diff --git a/osu.Game.Tests/Chat/TestSceneChannelManager.cs b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
index 0ec21a4c7b..5e22101e5c 100644
--- a/osu.Game.Tests/Chat/TestSceneChannelManager.cs
+++ b/osu.Game.Tests/Chat/TestSceneChannelManager.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -19,6 +20,7 @@ namespace osu.Game.Tests.Chat
{
private ChannelManager channelManager;
private int currentMessageId;
+ private List sentMessages;
[SetUp]
public void Setup() => Schedule(() =>
@@ -34,6 +36,7 @@ namespace osu.Game.Tests.Chat
AddStep("register request handling", () =>
{
currentMessageId = 0;
+ sentMessages = new List();
((DummyAPIAccess)API).HandleRequest = req =>
{
@@ -44,16 +47,11 @@ namespace osu.Game.Tests.Chat
return true;
case PostMessageRequest postMessage:
- postMessage.TriggerSuccess(new Message(++currentMessageId)
- {
- IsAction = postMessage.Message.IsAction,
- ChannelId = postMessage.Message.ChannelId,
- Content = postMessage.Message.Content,
- Links = postMessage.Message.Links,
- Timestamp = postMessage.Message.Timestamp,
- Sender = postMessage.Message.Sender
- });
+ handlePostMessageRequest(postMessage);
+ return true;
+ case MarkChannelAsReadRequest markRead:
+ handleMarkChannelAsReadRequest(markRead);
return true;
}
@@ -83,12 +81,65 @@ namespace osu.Game.Tests.Chat
AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to"));
}
+ [Test]
+ public void TestMarkAsReadIgnoringLocalMessages()
+ {
+ Channel channel = null;
+
+ AddStep("join channel and select it", () =>
+ {
+ channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public));
+ channelManager.CurrentChannel.Value = channel;
+ });
+
+ AddStep("post message", () => channelManager.PostMessage("Something interesting"));
+
+ AddStep("post /help command", () => channelManager.PostCommand("help", channel));
+ AddStep("post /me command with no action", () => channelManager.PostCommand("me", channel));
+ AddStep("post /join command with no channel", () => channelManager.PostCommand("join", channel));
+ AddStep("post /join command with non-existent channel", () => channelManager.PostCommand("join i-dont-exist", channel));
+ AddStep("post non-existent command", () => channelManager.PostCommand("non-existent-cmd arg", channel));
+
+ AddStep("mark channel as read", () => channelManager.MarkChannelAsRead(channel));
+ AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id);
+ }
+
+ private void handlePostMessageRequest(PostMessageRequest request)
+ {
+ var message = new Message(++currentMessageId)
+ {
+ IsAction = request.Message.IsAction,
+ ChannelId = request.Message.ChannelId,
+ Content = request.Message.Content,
+ Links = request.Message.Links,
+ Timestamp = request.Message.Timestamp,
+ Sender = request.Message.Sender
+ };
+
+ sentMessages.Add(message);
+ request.TriggerSuccess(message);
+ }
+
+ private void handleMarkChannelAsReadRequest(MarkChannelAsReadRequest request)
+ {
+ // only accept messages that were sent through the API
+ if (sentMessages.Contains(request.Message))
+ {
+ request.TriggerSuccess();
+ }
+ else
+ {
+ request.TriggerFailure(new APIException("unknown message!", null));
+ }
+ }
+
private Channel createChannel(int id, ChannelType type) => new Channel(new User())
{
Id = id,
Name = $"Channel {id}",
Topic = $"Topic of channel {id} with type {type}",
Type = type,
+ LastMessageId = 0,
};
private class ChannelManagerContainer : CompositeDrawable
diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
index aed28f5f84..3bf6aaac7a 100644
--- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
+++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs
@@ -204,7 +204,7 @@ namespace osu.Game.Tests.Gameplay
this.resources = resources;
}
- protected override ISkin GetSkin() => new TestSkin("test-sample", resources);
+ protected internal override ISkin GetSkin() => new TestSkin("test-sample", resources);
}
private class TestDrawableStoryboardSample : DrawableStoryboardSample
diff --git a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
index c15d804a19..aadabec100 100644
--- a/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
+++ b/osu.Game.Tests/Skins/TestSceneSkinConfigurationLookup.cs
@@ -133,11 +133,12 @@ namespace osu.Game.Tests.Skins
[Test]
public void TestEmptyComboColoursNoFallback()
{
- AddStep("Add custom combo colours to user skin", () => userSource.Configuration.AddComboColours(
+ AddStep("Add custom combo colours to user skin", () => userSource.Configuration.CustomComboColours = new List
+ {
new Color4(100, 150, 200, 255),
new Color4(55, 110, 166, 255),
new Color4(75, 125, 175, 255)
- ));
+ });
AddStep("Disallow default colours fallback in beatmap skin", () => beatmapSource.Configuration.AllowDefaultComboColoursFallback = false);
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/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
index 13e84e335d..e560c81fb2 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
@@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Gameplay
this.beatmapSkin = beatmapSkin;
}
- protected override ISkin GetSkin() => beatmapSkin;
+ protected internal override ISkin GetSkin() => beatmapSkin;
}
private class TestOsuRuleset : OsuRuleset
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
index 17fe09f2c6..0441c5641e 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayLeaderboard.cs
@@ -40,6 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
});
AddStep("add local player", () => createLeaderboardScore(playerScore, new User { Username = "You", Id = 3 }, true));
+ AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
}
@@ -83,19 +84,38 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("add frenzibyte", () => createRandomScore(new User { Username = "frenzibyte", Id = 14210502 }));
}
+ [Test]
+ public void TestMaxHeight()
+ {
+ int playerNumber = 1;
+ AddRepeatStep("add 3 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 3);
+ checkHeight(4);
+
+ AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
+ checkHeight(8);
+
+ AddRepeatStep("add 4 other players", () => createRandomScore(new User { Username = $"Player {playerNumber++}" }), 4);
+ checkHeight(8);
+
+ void checkHeight(int panelCount)
+ => AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
+ }
+
private void createRandomScore(User user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user);
private void createLeaderboardScore(BindableDouble score, User user, bool isTracked = false)
{
- var leaderboardScore = leaderboard.AddPlayer(user, isTracked);
+ var leaderboardScore = leaderboard.Add(user, isTracked);
leaderboardScore.TotalScore.BindTo(score);
}
private class TestGameplayLeaderboard : GameplayLeaderboard
{
+ public float Spacing => Flow.Spacing.Y;
+
public bool CheckPositionByUsername(string username, int? expectedPosition)
{
- var scoreItem = this.FirstOrDefault(i => i.User?.Username == username);
+ var scoreItem = Flow.FirstOrDefault(i => i.User?.Username == username);
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
}
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
index b7e92a79a0..3017428039 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs
@@ -12,6 +12,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
+using osu.Game.Skinning;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@@ -142,6 +143,22 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("return value", () => config.SetValue(OsuSetting.KeyOverlay, keyCounterVisibleValue));
}
+ [Test]
+ public void TestHiddenHUDDoesntBlockSkinnableComponentsLoad()
+ {
+ HUDVisibilityMode originalConfigValue = default;
+
+ AddStep("get original config value", () => originalConfigValue = config.Get(OsuSetting.HUDVisibilityMode));
+
+ AddStep("set hud to never show", () => config.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
+
+ createNew();
+ AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
+ AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType().Single().ComponentsLoaded);
+
+ AddStep("set original config value", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
+ }
+
private void createNew(Action action = null)
{
AddStep("create overlay", () =>
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
new file mode 100644
index 0000000000..e58f85b0b3
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneSideOverlays.cs
@@ -0,0 +1,42 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Overlays;
+using osu.Game.Tests.Visual.Navigation;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ public class TestSceneSideOverlays : OsuGameTestScene
+ {
+ [SetUpSteps]
+ public override void SetUpSteps()
+ {
+ base.SetUpSteps();
+
+ AddAssert("no screen offset applied", () => Game.ScreenOffsetContainer.X == 0f);
+ AddUntilStep("wait for overlays", () => Game.Settings.IsLoaded && Game.Notifications.IsLoaded);
+ }
+
+ [Test]
+ public void TestScreenOffsettingOnSettingsOverlay()
+ {
+ AddStep("open settings", () => Game.Settings.Show());
+ AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == SettingsPanel.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
+
+ AddStep("hide settings", () => Game.Settings.Hide());
+ AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ }
+
+ [Test]
+ public void TestScreenOffsettingOnNotificationOverlay()
+ {
+ AddStep("open notifications", () => Game.Notifications.Show());
+ AddUntilStep("right screen offset applied", () => Game.ScreenOffsetContainer.X == -NotificationOverlay.WIDTH * TestOsuGame.SIDE_OVERLAY_OFFSET_RATIO);
+
+ AddStep("hide notifications", () => Game.Notifications.Hide());
+ AddUntilStep("screen offset removed", () => Game.ScreenOffsetContainer.X == 0f);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs
new file mode 100644
index 0000000000..af874cec91
--- /dev/null
+++ b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs
@@ -0,0 +1,55 @@
+// 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.Containers;
+using osu.Framework.Testing;
+using osu.Game.Rulesets;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Rulesets.Osu.Mods;
+using osu.Game.Screens.Play;
+
+namespace osu.Game.Tests.Visual.Mods
+{
+ public class TestSceneModFailCondition : ModTestScene
+ {
+ private bool restartRequested;
+
+ protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
+
+ protected override TestPlayer CreateModPlayer(Ruleset ruleset)
+ {
+ var player = base.CreateModPlayer(ruleset);
+ player.RestartRequested = () => restartRequested = true;
+ return player;
+ }
+
+ protected override bool AllowFail => true;
+
+ [SetUpSteps]
+ public void SetUp()
+ {
+ AddStep("reset flag", () => restartRequested = false);
+ }
+
+ [Test]
+ public void TestRestartOnFailDisabled() => CreateModTest(new ModTestData
+ {
+ Autoplay = false,
+ Mod = new OsuModSuddenDeath(),
+ PassCondition = () => !restartRequested && Player.ChildrenOfType().Single().State.Value == Visibility.Visible
+ });
+
+ [Test]
+ public void TestRestartOnFailEnabled() => CreateModTest(new ModTestData
+ {
+ Autoplay = false,
+ Mod = new OsuModSuddenDeath
+ {
+ Restart = { Value = true }
+ },
+ PassCondition = () => restartRequested && Player.ChildrenOfType().Single().State.Value == Visibility.Hidden
+ });
+ }
+}
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/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 e9fae32335..65b1d6d53a 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs
@@ -8,6 +8,8 @@ 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;
@@ -26,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;
@@ -41,7 +43,7 @@ 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()
@@ -51,8 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_1_ID }, true);
OnlinePlayDependencies.Client.AddUser(new User { Id = PLAYER_2_ID }, true);
- playingUserIds.Add(PLAYER_1_ID);
- playingUserIds.Add(PLAYER_2_ID);
+ playingUsers.Add(new MultiplayerRoomUser(PLAYER_1_ID));
+ playingUsers.Add(new MultiplayerRoomUser(PLAYER_2_ID));
});
loadSpectateScreen(false);
@@ -78,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()
{
@@ -254,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));
@@ -269,7 +303,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
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 8121492a0b..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;
@@ -51,12 +52,13 @@ 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);
- OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true);
+ multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new User { Id = user }, true));
}
Children = new Drawable[]
@@ -64,9 +66,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
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..c4ebc13245 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs
@@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
@@ -48,9 +49,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1);
- AddStep("add non-resolvable user", () => Client.AddNullUser(-3));
+ AddStep("add non-resolvable user", () => Client.AddNullUser());
+ AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1);
AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2);
+
+ AddStep("kick null user", () => this.ChildrenOfType().Single(p => p.User.User == null)
+ .ChildrenOfType().Single().TriggerClick());
+
+ AddAssert("null user kicked", () => Client.Room.AsNonNull().Users.Count == 1);
}
[Test]
@@ -155,6 +162,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/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
new file mode 100644
index 0000000000..ff06d4d9c7
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs
@@ -0,0 +1,51 @@
+// 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.Game.Online.Rooms;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Scoring;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerResults : ScreenTestScene
+ {
+ [Test]
+ public void TestDisplayResults()
+ {
+ MultiplayerResultsScreen screen = null;
+
+ AddStep("show results screen", () =>
+ {
+ var rulesetInfo = new OsuRuleset().RulesetInfo;
+ var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
+
+ var score = new ScoreInfo
+ {
+ Rank = ScoreRank.B,
+ TotalScore = 987654,
+ Accuracy = 0.8,
+ MaxCombo = 500,
+ Combo = 250,
+ Beatmap = beatmapInfo,
+ User = new User { Username = "Test user" },
+ Date = DateTimeOffset.Now,
+ OnlineScoreID = 12345,
+ Ruleset = rulesetInfo,
+ };
+
+ PlaylistItem playlistItem = new PlaylistItem
+ {
+ BeatmapID = beatmapInfo.ID,
+ };
+
+ Stack.Push(screen = new MultiplayerResultsScreen(score, 1, playlistItem));
+ });
+
+ AddUntilStep("wait for loaded", () => screen.IsLoaded);
+ }
+ }
+}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
new file mode 100644
index 0000000000..0a8bda7ec0
--- /dev/null
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs
@@ -0,0 +1,61 @@
+// 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 NUnit.Framework;
+using osu.Framework.Bindables;
+using osu.Game.Online.Rooms;
+using osu.Game.Rulesets.Osu;
+using osu.Game.Scoring;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Multiplayer
+{
+ public class TestSceneMultiplayerTeamResults : ScreenTestScene
+ {
+ [TestCase(7483253, 1048576)]
+ [TestCase(1048576, 7483253)]
+ [TestCase(1048576, 1048576)]
+ public void TestDisplayTeamResults(int team1Score, int team2Score)
+ {
+ MultiplayerResultsScreen screen = null;
+
+ AddStep("show results screen", () =>
+ {
+ var rulesetInfo = new OsuRuleset().RulesetInfo;
+ var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
+
+ var score = new ScoreInfo
+ {
+ Rank = ScoreRank.B,
+ TotalScore = 987654,
+ Accuracy = 0.8,
+ MaxCombo = 500,
+ Combo = 250,
+ Beatmap = beatmapInfo,
+ User = new User { Username = "Test user" },
+ Date = DateTimeOffset.Now,
+ OnlineScoreID = 12345,
+ Ruleset = rulesetInfo,
+ };
+
+ PlaylistItem playlistItem = new PlaylistItem
+ {
+ BeatmapID = beatmapInfo.ID,
+ };
+
+ SortedDictionary teamScores = new SortedDictionary
+ {
+ { 0, new BindableInt(team1Score) },
+ { 1, new BindableInt(team2Score) }
+ };
+
+ Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, playlistItem, teamScores));
+ });
+
+ AddUntilStep("wait for loaded", () => screen.IsLoaded);
+ }
+ }
+}
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/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
index f9a991f756..c9a1471e41 100644
--- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
+++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Framework.Screens;
@@ -95,6 +96,8 @@ namespace osu.Game.Tests.Visual.Navigation
public class TestOsuGame : OsuGame
{
+ public new const float SIDE_OVERLAY_OFFSET_RATIO = OsuGame.SIDE_OVERLAY_OFFSET_RATIO;
+
public new ScreenStack ScreenStack => base.ScreenStack;
public new BackButton BackButton => base.BackButton;
@@ -103,7 +106,11 @@ namespace osu.Game.Tests.Visual.Navigation
public new ScoreManager ScoreManager => base.ScoreManager;
- public new SettingsPanel Settings => base.Settings;
+ public new Container ScreenOffsetContainer => base.ScreenOffsetContainer;
+
+ public new SettingsOverlay Settings => base.Settings;
+
+ public new NotificationOverlay Notifications => base.Notifications;
public new MusicController MusicController => base.MusicController;
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/Playlists/TestScenePlaylistsLoungeSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
index ecdb046203..aff0e7ba4b 100644
--- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
+++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsLoungeSubScreen.cs
@@ -62,6 +62,24 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms[^1]));
}
+ [Test]
+ public void TestEnteringRoomTakesLeaseOnSelection()
+ {
+ AddStep("add rooms", () => RoomManager.AddRooms(1));
+
+ AddAssert("selected room is not disabled", () => !OnlinePlayDependencies.SelectedRoom.Disabled);
+
+ AddStep("select room", () => roomsContainer.Rooms[0].TriggerClick());
+ AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null);
+
+ AddStep("enter room", () => roomsContainer.Rooms[0].TriggerClick());
+
+ AddUntilStep("wait for match load", () => Stack.CurrentScreen is PlaylistsRoomSubScreen);
+
+ AddAssert("selected room is non-null", () => OnlinePlayDependencies.SelectedRoom.Value != null);
+ AddAssert("selected room is disabled", () => OnlinePlayDependencies.SelectedRoom.Disabled);
+ }
+
private bool checkRoomVisible(DrawableRoom room) =>
loungeScreen.ChildrenOfType().First().ScreenSpaceDrawQuad
.Contains(room.ScreenSpaceDrawQuad.Centre);
diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
index a62980addf..da474a64ba 100644
--- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
+++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs
@@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Settings
new TabletSettings(tabletHandler)
{
RelativeSizeAxes = Axes.None,
- Width = SettingsPanel.WIDTH,
+ Width = SettingsPanel.PANEL_WIDTH,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
index e0d76b3e4a..f8652573f4 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs
@@ -1,8 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -174,6 +176,60 @@ namespace osu.Game.Tests.Visual.UserInterface
checkBindableAtValue("Circle Size", null);
}
+ [Test]
+ public void TestModSettingChangeTracker()
+ {
+ ModSettingChangeTracker tracker = null;
+ Queue settingsChangedQueue = null;
+
+ setBeatmapWithDifficultyParameters(5);
+
+ AddStep("add mod settings change tracker", () =>
+ {
+ settingsChangedQueue = new Queue();
+
+ tracker = new ModSettingChangeTracker(modDifficultyAdjust.Yield())
+ {
+ SettingChanged = settingsChangedQueue.Enqueue
+ };
+ });
+
+ AddAssert("no settings changed", () => settingsChangedQueue.Count == 0);
+
+ setSliderValue("Circle Size", 3);
+
+ settingsChangedFired();
+
+ setSliderValue("Circle Size", 5);
+ checkBindableAtValue("Circle Size", 5);
+
+ settingsChangedFired();
+
+ AddStep("reset mod settings", () => modDifficultyAdjust.CircleSize.SetDefault());
+ checkBindableAtValue("Circle Size", null);
+
+ settingsChangedFired();
+
+ setExtendedLimits(true);
+
+ settingsChangedFired();
+
+ AddStep("dispose tracker", () =>
+ {
+ tracker.Dispose();
+ tracker = null;
+ });
+
+ void settingsChangedFired()
+ {
+ AddAssert("setting changed event fired", () =>
+ {
+ settingsChangedQueue.Dequeue();
+ return settingsChangedQueue.Count == 0;
+ });
+ }
+ }
+
private void resetToDefault(string name)
{
AddStep($"Reset {name} to default", () =>
diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs
index 5477e4a0f8..9c85fa0c9c 100644
--- a/osu.Game.Tests/WaveformTestBeatmap.cs
+++ b/osu.Game.Tests/WaveformTestBeatmap.cs
@@ -53,7 +53,7 @@ namespace osu.Game.Tests
protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile));
- protected override ISkin GetSkin() => null;
+ protected internal override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
index acd5d53310..11b5cc7556 100644
--- a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
+++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Tournament.Tests.Components
public TestSceneMatchScoreDisplay()
{
- Add(new MatchScoreDisplay
+ Add(new TournamentMatchScoreDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs
similarity index 97%
rename from osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
rename to osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs
index 695c6d6f3e..994dee4da0 100644
--- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs
@@ -16,7 +16,8 @@ using osuTK;
namespace osu.Game.Tournament.Screens.Gameplay.Components
{
- public class MatchScoreDisplay : CompositeDrawable
+ // TODO: Update to derive from osu-side class?
+ public class TournamentMatchScoreDisplay : CompositeDrawable
{
private const float bar_height = 18;
@@ -29,7 +30,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components
private readonly Drawable score1Bar;
private readonly Drawable score2Bar;
- public MatchScoreDisplay()
+ public TournamentMatchScoreDisplay()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
index f61506d7f2..540b45eb56 100644
--- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
+++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs
@@ -86,7 +86,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
},
}
},
- scoreDisplay = new MatchScoreDisplay
+ scoreDisplay = new TournamentMatchScoreDisplay
{
Y = -147,
Anchor = Anchor.BottomCentre,
@@ -148,7 +148,7 @@ namespace osu.Game.Tournament.Screens.Gameplay
}
private ScheduledDelegate scheduledOperation;
- private MatchScoreDisplay scoreDisplay;
+ private TournamentMatchScoreDisplay scoreDisplay;
private TourneyState lastState;
private MatchHeader header;
diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs
index cd0e601a2f..7a43fee013 100644
--- a/osu.Game.Tournament/TournamentGame.cs
+++ b/osu.Game.Tournament/TournamentGame.cs
@@ -26,8 +26,8 @@ namespace osu.Game.Tournament
{
public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE;
- public static readonly Color4 COLOUR_RED = Color4Extensions.FromHex("#AA1414");
- public static readonly Color4 COLOUR_BLUE = Color4Extensions.FromHex("#1462AA");
+ public static readonly Color4 COLOUR_RED = new OsuColour().TeamColourRed;
+ public static readonly Color4 COLOUR_BLUE = new OsuColour().TeamColourBlue;
public static readonly Color4 ELEMENT_BACKGROUND_COLOUR = Color4Extensions.FromHex("#fff");
public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000");
diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs
index 0d16294c68..4a78ceb299 100644
--- a/osu.Game/Beatmaps/BeatmapManager.cs
+++ b/osu.Game/Beatmaps/BeatmapManager.cs
@@ -534,7 +534,7 @@ namespace osu.Game.Beatmaps
protected override IBeatmap GetBeatmap() => beatmap;
protected override Texture GetBackground() => null;
protected override Track GetBeatmapTrack() => null;
- protected override ISkin GetSkin() => null;
+ protected internal override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
}
}
diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
index d78ffbbfb6..45112ae74c 100644
--- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs
@@ -128,7 +128,7 @@ namespace osu.Game.Beatmaps
return storyboard;
}
- protected override ISkin GetSkin()
+ protected internal override ISkin GetSkin()
{
try
{
diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
index ea7f45e53f..acfd01a3c8 100644
--- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs
@@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps
protected override Track GetBeatmapTrack() => GetVirtualTrack();
- protected override ISkin GetSkin() => null;
+ protected internal override ISkin GetSkin() => null;
public override Stream GetStream(string storagePath) => null;
diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
index 41c85db063..853a590595 100644
--- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs
+++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using System.Collections.Generic;
using osuTK.Graphics;
@@ -13,9 +14,17 @@ namespace osu.Game.Beatmaps.Formats
///
IReadOnlyList ComboColours { get; }
+ ///
+ /// The list of custom combo colours.
+ /// If non-empty, will return these colours;
+ /// if empty, will fall back to default combo colours.
+ ///
+ List CustomComboColours { get; }
+
///
/// Adds combo colours to the list.
///
+ [Obsolete("Use CustomComboColours directly.")] // can be removed 20220215
void AddComboColours(params Color4[] colours);
}
}
diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
index b39890084f..20080308f9 100644
--- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs
@@ -123,7 +123,7 @@ namespace osu.Game.Beatmaps.Formats
{
if (!(output is IHasComboColours tHasComboColours)) return;
- tHasComboColours.AddComboColours(colour);
+ tHasComboColours.CustomComboColours.Add(colour);
}
else
{
diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs
index 662d24cc83..61760e69b0 100644
--- a/osu.Game/Beatmaps/WorkingBeatmap.cs
+++ b/osu.Game/Beatmaps/WorkingBeatmap.cs
@@ -327,7 +327,15 @@ namespace osu.Game.Beatmaps
public bool SkinLoaded => skin.IsResultAvailable;
public ISkin Skin => skin.Value;
- protected abstract ISkin GetSkin();
+ ///
+ /// Creates a new skin instance for this beatmap.
+ ///
+ ///
+ /// This should only be called externally in scenarios where it is explicitly desired to get a new instance of a skin
+ /// (e.g. for editing purposes, to avoid state pollution).
+ /// For standard reading purposes, should always be used directly.
+ ///
+ protected internal abstract ISkin GetSkin();
private readonly RecyclableLazy skin;
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 60a0d5a0ac..9b0d7f51da 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -201,6 +201,8 @@ namespace osu.Game.Configuration
public Func LookupKeyBindings { get; set; }
}
+ // IMPORTANT: These are used in user configuration files.
+ // The naming of these keys should not be changed once they are deployed in a release, unless migration logic is also added.
public enum OsuSetting
{
Ruleset,
diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs
index 1f87c06dd2..d7cfc4094c 100644
--- a/osu.Game/Graphics/OsuColour.cs
+++ b/osu.Game/Graphics/OsuColour.cs
@@ -130,6 +130,9 @@ namespace osu.Game.Graphics
return Gray(brightness > 0.5f ? 0.2f : 0.9f);
}
+ public readonly Color4 TeamColourRed = Color4Extensions.FromHex("#AA1414");
+ public readonly Color4 TeamColourBlue = Color4Extensions.FromHex("#1462AA");
+
// See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less
public readonly Color4 PurpleLighter = Color4Extensions.FromHex(@"eeeeff");
public readonly Color4 PurpleLight = Color4Extensions.FromHex(@"aa88ff");
diff --git a/osu.Game/Localisation/AudioSettingsStrings.cs b/osu.Game/Localisation/AudioSettingsStrings.cs
new file mode 100644
index 0000000000..aa6eabd7d1
--- /dev/null
+++ b/osu.Game/Localisation/AudioSettingsStrings.cs
@@ -0,0 +1,64 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class AudioSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.AudioSettings";
+
+ ///
+ /// "Audio"
+ ///
+ public static LocalisableString AudioSectionHeader => new TranslatableString(getKey(@"audio_section_header"), @"Audio");
+
+ ///
+ /// "Devices"
+ ///
+ public static LocalisableString AudioDevicesHeader => new TranslatableString(getKey(@"audio_devices_header"), @"Devices");
+
+ ///
+ /// "Volume"
+ ///
+ public static LocalisableString VolumeHeader => new TranslatableString(getKey(@"volume_header"), @"Volume");
+
+ ///
+ /// "Master"
+ ///
+ public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
+
+ ///
+ /// "Master (window inactive)"
+ ///
+ public static LocalisableString MasterVolumeInactive => new TranslatableString(getKey(@"master_volume_inactive"), @"Master (window inactive)");
+
+ ///
+ /// "Effect"
+ ///
+ public static LocalisableString EffectVolume => new TranslatableString(getKey(@"effect_volume"), @"Effect");
+
+ ///
+ /// "Music"
+ ///
+ public static LocalisableString MusicVolume => new TranslatableString(getKey(@"music_volume"), @"Music");
+
+ ///
+ /// "Offset Adjustment"
+ ///
+ public static LocalisableString OffsetHeader => new TranslatableString(getKey(@"offset_header"), @"Offset Adjustment");
+
+ ///
+ /// "Audio offset"
+ ///
+ public static LocalisableString AudioOffset => new TranslatableString(getKey(@"audio_offset"), @"Audio offset");
+
+ ///
+ /// "Offset wizard"
+ ///
+ public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs
index bf488d2590..432c1c6255 100644
--- a/osu.Game/Localisation/CommonStrings.cs
+++ b/osu.Game/Localisation/CommonStrings.cs
@@ -14,11 +14,21 @@ namespace osu.Game.Localisation
///
public static LocalisableString Cancel => new TranslatableString(getKey(@"cancel"), @"Cancel");
+ ///
+ /// "Clear"
+ ///
+ public static LocalisableString Clear => new TranslatableString(getKey(@"clear"), @"Clear");
+
///
/// "Enabled"
///
public static LocalisableString Enabled => new TranslatableString(getKey(@"enabled"), @"Enabled");
+ ///
+ /// "Default"
+ ///
+ public static LocalisableString Default => new TranslatableString(getKey(@"default"), @"Default");
+
///
/// "Width"
///
@@ -31,4 +41,4 @@ namespace osu.Game.Localisation
private static string getKey(string key) => $@"{prefix}:{key}";
}
-}
\ No newline at end of file
+}
diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs
new file mode 100644
index 0000000000..dd21739096
--- /dev/null
+++ b/osu.Game/Localisation/DebugSettingsStrings.cs
@@ -0,0 +1,49 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class DebugSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings";
+
+ ///
+ /// "Debug"
+ ///
+ public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Show log overlay"
+ ///
+ public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay");
+
+ ///
+ /// "Bypass front-to-back render pass"
+ ///
+ public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass");
+
+ ///
+ /// "Import files"
+ ///
+ public static LocalisableString ImportFiles => new TranslatableString(getKey(@"import_files"), @"Import files");
+
+ ///
+ /// "Memory"
+ ///
+ public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory");
+
+ ///
+ /// "Clear all caches"
+ ///
+ public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs
new file mode 100644
index 0000000000..6d6381b429
--- /dev/null
+++ b/osu.Game/Localisation/GameplaySettingsStrings.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 osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class GameplaySettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GameplaySettings";
+
+ ///
+ /// "Gameplay"
+ ///
+ public static LocalisableString GameplaySectionHeader => new TranslatableString(getKey(@"gameplay_section_header"), @"Gameplay");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Background dim"
+ ///
+ public static LocalisableString BackgroundDim => new TranslatableString(getKey(@"dim"), @"Background dim");
+
+ ///
+ /// "Background blur"
+ ///
+ public static LocalisableString BackgroundBlur => new TranslatableString(getKey(@"blur"), @"Background blur");
+
+ ///
+ /// "Lighten playfield during breaks"
+ ///
+ public static LocalisableString LightenDuringBreaks => new TranslatableString(getKey(@"lighten_during_breaks"), @"Lighten playfield during breaks");
+
+ ///
+ /// "HUD overlay visibility mode"
+ ///
+ public static LocalisableString HUDVisibilityMode => new TranslatableString(getKey(@"hud_visibility_mode"), @"HUD overlay visibility mode");
+
+ ///
+ /// "Show difficulty graph on progress bar"
+ ///
+ public static LocalisableString ShowDifficultyGraph => new TranslatableString(getKey(@"show_difficulty_graph"), @"Show difficulty graph on progress bar");
+
+ ///
+ /// "Show health display even when you can't fail"
+ ///
+ public static LocalisableString ShowHealthDisplayWhenCantFail => new TranslatableString(getKey(@"show_health_display_when_cant_fail"), @"Show health display even when you can't fail");
+
+ ///
+ /// "Fade playfield to red when health is low"
+ ///
+ public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low");
+
+ ///
+ /// "Always show key overlay"
+ ///
+ public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay");
+
+ ///
+ /// "Positional hitsounds"
+ ///
+ public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds");
+
+ ///
+ /// "Always play first combo break sound"
+ ///
+ public static LocalisableString AlwaysPlayFirstComboBreak => new TranslatableString(getKey(@"always_play_first_combo_break"), @"Always play first combo break sound");
+
+ ///
+ /// "Score display mode"
+ ///
+ public static LocalisableString ScoreDisplayMode => new TranslatableString(getKey(@"score_display_mode"), @"Score display mode");
+
+ ///
+ /// "Disable Windows key during gameplay"
+ ///
+ public static LocalisableString DisableWinKey => new TranslatableString(getKey(@"disable_win_key"), @"Disable Windows key during gameplay");
+
+ ///
+ /// "Mods"
+ ///
+ public static LocalisableString ModsHeader => new TranslatableString(getKey(@"mods_header"), @"Mods");
+
+ ///
+ /// "Increase visibility of first object when visual impairment mods are enabled"
+ ///
+ public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs
new file mode 100644
index 0000000000..a60e4891f4
--- /dev/null
+++ b/osu.Game/Localisation/GeneralSettingsStrings.cs
@@ -0,0 +1,59 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class GeneralSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GeneralSettings";
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralSectionHeader => new TranslatableString(getKey(@"general_section_header"), @"General");
+
+ ///
+ /// "Language"
+ ///
+ public static LocalisableString LanguageHeader => new TranslatableString(getKey(@"language_header"), @"Language");
+
+ ///
+ /// "Language"
+ ///
+ public static LocalisableString LanguageDropdown => new TranslatableString(getKey(@"language_dropdown"), @"Language");
+
+ ///
+ /// "Prefer metadata in original language"
+ ///
+ public static LocalisableString PreferOriginalMetadataLanguage => new TranslatableString(getKey(@"prefer_original"), @"Prefer metadata in original language");
+
+ ///
+ /// "Updates"
+ ///
+ public static LocalisableString UpdateHeader => new TranslatableString(getKey(@"update_header"), @"Updates");
+
+ ///
+ /// "Release stream"
+ ///
+ public static LocalisableString ReleaseStream => new TranslatableString(getKey(@"release_stream"), @"Release stream");
+
+ ///
+ /// "Check for updates"
+ ///
+ public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates");
+
+ ///
+ /// "Open osu! folder"
+ ///
+ public static LocalisableString OpenOsuFolder => new TranslatableString(getKey(@"open_osu_folder"), @"Open osu! folder");
+
+ ///
+ /// "Change folder location..."
+ ///
+ public static LocalisableString ChangeFolderLocation => new TranslatableString(getKey(@"change_folder_location"), @"Change folder location...");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/GraphicsSettingsStrings.cs b/osu.Game/Localisation/GraphicsSettingsStrings.cs
new file mode 100644
index 0000000000..0e384f983f
--- /dev/null
+++ b/osu.Game/Localisation/GraphicsSettingsStrings.cs
@@ -0,0 +1,119 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class GraphicsSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.GraphicsSettings";
+
+ ///
+ /// "Graphics"
+ ///
+ public static LocalisableString GraphicsSectionHeader => new TranslatableString(getKey(@"graphics_section_header"), @"Graphics");
+
+ ///
+ /// "Renderer"
+ ///
+ public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
+
+ ///
+ /// "Frame limiter"
+ ///
+ public static LocalisableString FrameLimiter => new TranslatableString(getKey(@"frame_limiter"), @"Frame limiter");
+
+ ///
+ /// "Threading mode"
+ ///
+ public static LocalisableString ThreadingMode => new TranslatableString(getKey(@"threading_mode"), @"Threading mode");
+
+ ///
+ /// "Show FPS"
+ ///
+ public static LocalisableString ShowFPS => new TranslatableString(getKey(@"show_fps"), @"Show FPS");
+
+ ///
+ /// "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. "2x refresh rate" is recommended."
+ ///
+ public static LocalisableString UnlimitedFramesNote => new TranslatableString(getKey(@"unlimited_frames_note"), @"Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. ""2x refresh rate"" is recommended.");
+
+ ///
+ /// "Layout"
+ ///
+ public static LocalisableString LayoutHeader => new TranslatableString(getKey(@"layout_header"), @"Layout");
+
+ ///
+ /// "Screen mode"
+ ///
+ public static LocalisableString ScreenMode => new TranslatableString(getKey(@"screen_mode"), @"Screen mode");
+
+ ///
+ /// "Resolution"
+ ///
+ public static LocalisableString Resolution => new TranslatableString(getKey(@"resolution"), @"Resolution");
+
+ ///
+ /// "UI scaling"
+ ///
+ public static LocalisableString UIScaling => new TranslatableString(getKey(@"ui_scaling"), @"UI scaling");
+
+ ///
+ /// "Screen scaling"
+ ///
+ public static LocalisableString ScreenScaling => new TranslatableString(getKey(@"screen_scaling"), @"Screen scaling");
+
+ ///
+ /// "Horizontal position"
+ ///
+ public static LocalisableString HorizontalPosition => new TranslatableString(getKey(@"horizontal_position"), @"Horizontal position");
+
+ ///
+ /// "Vertical position"
+ ///
+ public static LocalisableString VerticalPosition => new TranslatableString(getKey(@"vertical_position"), @"Vertical position");
+
+ ///
+ /// "Horizontal scale"
+ ///
+ public static LocalisableString HorizontalScale => new TranslatableString(getKey(@"horizontal_scale"), @"Horizontal scale");
+
+ ///
+ /// "Vertical scale"
+ ///
+ public static LocalisableString VerticalScale => new TranslatableString(getKey(@"vertical_scale"), @"Vertical scale");
+
+ ///
+ /// "Running without fullscreen mode will increase your input latency!"
+ ///
+ public static LocalisableString NotFullscreenNote => new TranslatableString(getKey(@"not_fullscreen_note"), @"Running without fullscreen mode will increase your input latency!");
+
+ ///
+ /// "Detail Settings"
+ ///
+ public static LocalisableString DetailSettingsHeader => new TranslatableString(getKey(@"detail_settings_header"), @"Detail Settings");
+
+ ///
+ /// "Storyboard / video"
+ ///
+ public static LocalisableString StoryboardVideo => new TranslatableString(getKey(@"storyboard_video"), @"Storyboard / video");
+
+ ///
+ /// "Hit lighting"
+ ///
+ public static LocalisableString HitLighting => new TranslatableString(getKey(@"hit_lighting"), @"Hit lighting");
+
+ ///
+ /// "Screenshot format"
+ ///
+ public static LocalisableString ScreenshotFormat => new TranslatableString(getKey(@"screenshot_format"), @"Screenshot format");
+
+ ///
+ /// "Show menu cursor in screenshots"
+ ///
+ public static LocalisableString ShowCursorInScreenshots => new TranslatableString(getKey(@"show_cursor_in_screenshots"), @"Show menu cursor in screenshots");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/InputSettingsStrings.cs b/osu.Game/Localisation/InputSettingsStrings.cs
new file mode 100644
index 0000000000..e46b4cecf3
--- /dev/null
+++ b/osu.Game/Localisation/InputSettingsStrings.cs
@@ -0,0 +1,59 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class InputSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.InputSettings";
+
+ ///
+ /// "Input"
+ ///
+ public static LocalisableString InputSectionHeader => new TranslatableString(getKey(@"input_section_header"), @"Input");
+
+ ///
+ /// "Global"
+ ///
+ public static LocalisableString GlobalKeyBindingHeader => new TranslatableString(getKey(@"global_key_binding_header"), @"Global");
+
+ ///
+ /// "Song Select"
+ ///
+ public static LocalisableString SongSelectSection => new TranslatableString(getKey(@"song_select_section"), @"Song Select");
+
+ ///
+ /// "In Game"
+ ///
+ public static LocalisableString InGameSection => new TranslatableString(getKey(@"in_game_section"), @"In Game");
+
+ ///
+ /// "Audio"
+ ///
+ public static LocalisableString AudioSection => new TranslatableString(getKey(@"audio_section"), @"Audio");
+
+ ///
+ /// "Editor"
+ ///
+ public static LocalisableString EditorSection => new TranslatableString(getKey(@"editor_section"), @"Editor");
+
+ ///
+ /// "Reset all bindings in section"
+ ///
+ public static LocalisableString ResetSectionButton => new TranslatableString(getKey(@"reset_section_button"), @"Reset all bindings in section");
+
+ ///
+ /// "key configuration"
+ ///
+ public static LocalisableString KeyBindingPanelHeader => new TranslatableString(getKey(@"key_binding_panel_header"), @"key configuration");
+
+ ///
+ /// "Customise your keys!"
+ ///
+ public static LocalisableString KeyBindingPanelDescription => new TranslatableString(getKey(@"key_binding_panel_description"), @"Customise your keys!");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/MaintenanceSettingsStrings.cs b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
new file mode 100644
index 0000000000..a0e1a9ddab
--- /dev/null
+++ b/osu.Game/Localisation/MaintenanceSettingsStrings.cs
@@ -0,0 +1,74 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class MaintenanceSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.MaintenanceSettings";
+
+ ///
+ /// "Maintenance"
+ ///
+ public static LocalisableString MaintenanceSectionHeader => new TranslatableString(getKey(@"maintenance_section_header"), @"Maintenance");
+
+ ///
+ /// "Select directory"
+ ///
+ public static LocalisableString SelectDirectory => new TranslatableString(getKey(@"select_directory"), @"Select directory");
+
+ ///
+ /// "Import beatmaps from stable"
+ ///
+ public static LocalisableString ImportBeatmapsFromStable => new TranslatableString(getKey(@"import_beatmaps_from_stable"), @"Import beatmaps from stable");
+
+ ///
+ /// "Delete ALL beatmaps"
+ ///
+ public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
+
+ ///
+ /// "Import scores from stable"
+ ///
+ public static LocalisableString ImportScoresFromStable => new TranslatableString(getKey(@"import_scores_from_stable"), @"Import scores from stable");
+
+ ///
+ /// "Delete ALL scores"
+ ///
+ public static LocalisableString DeleteAllScores => new TranslatableString(getKey(@"delete_all_scores"), @"Delete ALL scores");
+
+ ///
+ /// "Import skins from stable"
+ ///
+ public static LocalisableString ImportSkinsFromStable => new TranslatableString(getKey(@"import_skins_from_stable"), @"Import skins from stable");
+
+ ///
+ /// "Delete ALL skins"
+ ///
+ public static LocalisableString DeleteAllSkins => new TranslatableString(getKey(@"delete_all_skins"), @"Delete ALL skins");
+
+ ///
+ /// "Import collections from stable"
+ ///
+ public static LocalisableString ImportCollectionsFromStable => new TranslatableString(getKey(@"import_collections_from_stable"), @"Import collections from stable");
+
+ ///
+ /// "Delete ALL collections"
+ ///
+ public static LocalisableString DeleteAllCollections => new TranslatableString(getKey(@"delete_all_collections"), @"Delete ALL collections");
+
+ ///
+ /// "Restore all hidden difficulties"
+ ///
+ public static LocalisableString RestoreAllHiddenDifficulties => new TranslatableString(getKey(@"restore_all_hidden_difficulties"), @"Restore all hidden difficulties");
+
+ ///
+ /// "Restore all recently deleted beatmaps"
+ ///
+ public static LocalisableString RestoreAllRecentlyDeletedBeatmaps => new TranslatableString(getKey(@"restore_all_recently_deleted_beatmaps"), @"Restore all recently deleted beatmaps");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs
new file mode 100644
index 0000000000..111c068bbd
--- /dev/null
+++ b/osu.Game/Localisation/MultiplayerTeamResultsScreenStrings.cs
@@ -0,0 +1,24 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class MultiplayerTeamResultsScreenStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.MultiplayerTeamResultsScreen";
+
+ ///
+ /// "Team {0} wins!"
+ ///
+ public static LocalisableString TeamWins(string winner) => new TranslatableString(getKey(@"team_wins"), @"Team {0} wins!", winner);
+
+ ///
+ /// "The teams are tied!"
+ ///
+ public static LocalisableString TheTeamsAreTied => new TranslatableString(getKey(@"the_teams_are_tied"), @"The teams are tied!");
+
+ private static string getKey(string key) => $@"{prefix}:{key}";
+ }
+}
\ No newline at end of file
diff --git a/osu.Game/Localisation/OnlineSettingsStrings.cs b/osu.Game/Localisation/OnlineSettingsStrings.cs
new file mode 100644
index 0000000000..6862f4ac2c
--- /dev/null
+++ b/osu.Game/Localisation/OnlineSettingsStrings.cs
@@ -0,0 +1,69 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class OnlineSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.OnlineSettings";
+
+ ///
+ /// "Online"
+ ///
+ public static LocalisableString OnlineSectionHeader => new TranslatableString(getKey(@"online_section_header"), @"Online");
+
+ ///
+ /// "Alerts and Privacy"
+ ///
+ public static LocalisableString AlertsAndPrivacyHeader => new TranslatableString(getKey(@"alerts_and_privacy_header"), @"Alerts and Privacy");
+
+ ///
+ /// "Show a notification when someone mentions your name"
+ ///
+ public static LocalisableString NotifyOnMentioned => new TranslatableString(getKey(@"notify_on_mentioned"), @"Show a notification when someone mentions your name");
+
+ ///
+ /// "Show a notification when you receive a private message"
+ ///
+ public static LocalisableString NotifyOnPrivateMessage => new TranslatableString(getKey(@"notify_on_private_message"), @"Show a notification when you receive a private message");
+
+ ///
+ /// "Integrations"
+ ///
+ public static LocalisableString IntegrationsHeader => new TranslatableString(getKey(@"integrations_header"), @"Integrations");
+
+ ///
+ /// "Discord Rich Presence"
+ ///
+ public static LocalisableString DiscordRichPresence => new TranslatableString(getKey(@"discord_rich_presence"), @"Discord Rich Presence");
+
+ ///
+ /// "Web"
+ ///
+ public static LocalisableString WebHeader => new TranslatableString(getKey(@"web_header"), @"Web");
+
+ ///
+ /// "Warn about opening external links"
+ ///
+ public static LocalisableString ExternalLinkWarning => new TranslatableString(getKey(@"external_link_warning"), @"Warn about opening external links");
+
+ ///
+ /// "Prefer downloads without video"
+ ///
+ public static LocalisableString PreferNoVideo => new TranslatableString(getKey(@"prefer_no_video"), @"Prefer downloads without video");
+
+ ///
+ /// "Automatically download beatmaps when spectating"
+ ///
+ public static LocalisableString AutomaticallyDownloadWhenSpectating => new TranslatableString(getKey(@"automatically_download_when_spectating"), @"Automatically download beatmaps when spectating");
+
+ ///
+ /// "Show explicit content in search results"
+ ///
+ public static LocalisableString ShowExplicitContent => new TranslatableString(getKey(@"show_explicit_content"), @"Show explicit content in search results");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs
new file mode 100644
index 0000000000..f22b4d6bf5
--- /dev/null
+++ b/osu.Game/Localisation/SkinSettingsStrings.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class SkinSettingsStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.SkinSettings";
+
+ ///
+ /// "Skin"
+ ///
+ public static LocalisableString SkinSectionHeader => new TranslatableString(getKey(@"skin_section_header"), @"Skin");
+
+ ///
+ /// "Skin layout editor"
+ ///
+ public static LocalisableString SkinLayoutEditor => new TranslatableString(getKey(@"skin_layout_editor"), @"Skin layout editor");
+
+ ///
+ /// "Gameplay cursor size"
+ ///
+ public static LocalisableString GameplayCursorSize => new TranslatableString(getKey(@"gameplay_cursor_size"), @"Gameplay cursor size");
+
+ ///
+ /// "Adjust gameplay cursor size based on current beatmap"
+ ///
+ public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap");
+
+ ///
+ /// "Beatmap skins"
+ ///
+ public static LocalisableString BeatmapSkins => new TranslatableString(getKey(@"beatmap_skins"), @"Beatmap skins");
+
+ ///
+ /// "Beatmap colours"
+ ///
+ public static LocalisableString BeatmapColours => new TranslatableString(getKey(@"beatmap_colours"), @"Beatmap colours");
+
+ ///
+ /// "Beatmap hitsounds"
+ ///
+ public static LocalisableString BeatmapHitsounds => new TranslatableString(getKey(@"beatmap_hitsounds"), @"Beatmap hitsounds");
+
+ ///
+ /// "Export selected skin"
+ ///
+ public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs
new file mode 100644
index 0000000000..4be403edb4
--- /dev/null
+++ b/osu.Game/Localisation/UserInterfaceStrings.cs
@@ -0,0 +1,114 @@
+// 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.Localisation;
+
+namespace osu.Game.Localisation
+{
+ public static class UserInterfaceStrings
+ {
+ private const string prefix = @"osu.Game.Resources.Localisation.UserInterface";
+
+ ///
+ /// "User Interface"
+ ///
+ public static LocalisableString UserInterfaceSectionHeader => new TranslatableString(getKey(@"user_interface_section_header"), @"User Interface");
+
+ ///
+ /// "General"
+ ///
+ public static LocalisableString GeneralHeader => new TranslatableString(getKey(@"general_header"), @"General");
+
+ ///
+ /// "Rotate cursor when dragging"
+ ///
+ public static LocalisableString CursorRotation => new TranslatableString(getKey(@"cursor_rotation"), @"Rotate cursor when dragging");
+
+ ///
+ /// "Menu cursor size"
+ ///
+ public static LocalisableString MenuCursorSize => new TranslatableString(getKey(@"menu_cursor_size"), @"Menu cursor size");
+
+ ///
+ /// "Parallax"
+ ///
+ public static LocalisableString Parallax => new TranslatableString(getKey(@"parallax"), @"Parallax");
+
+ ///
+ /// "Hold-to-confirm activation time"
+ ///
+ public static LocalisableString HoldToConfirmActivationTime => new TranslatableString(getKey(@"hold_to_confirm_activation_time"), @"Hold-to-confirm activation time");
+
+ ///
+ /// "Main Menu"
+ ///
+ public static LocalisableString MainMenuHeader => new TranslatableString(getKey(@"main_menu_header"), @"Main Menu");
+
+ ///
+ /// "Interface voices"
+ ///
+ public static LocalisableString InterfaceVoices => new TranslatableString(getKey(@"interface_voices"), @"Interface voices");
+
+ ///
+ /// "osu! music theme"
+ ///
+ public static LocalisableString OsuMusicTheme => new TranslatableString(getKey(@"osu_music_theme"), @"osu! music theme");
+
+ ///
+ /// "Intro sequence"
+ ///
+ public static LocalisableString IntroSequence => new TranslatableString(getKey(@"intro_sequence"), @"Intro sequence");
+
+ ///
+ /// "Background source"
+ ///
+ public static LocalisableString BackgroundSource => new TranslatableString(getKey(@"background_source"), @"Background source");
+
+ ///
+ /// "Seasonal backgrounds"
+ ///
+ public static LocalisableString SeasonalBackgrounds => new TranslatableString(getKey(@"seasonal_backgrounds"), @"Seasonal backgrounds");
+
+ ///
+ /// "Changes to this setting will only apply with an active osu!supporter tag."
+ ///
+ public static LocalisableString NotSupporterNote => new TranslatableString(getKey(@"not_supporter_note"), @"Changes to this setting will only apply with an active osu!supporter tag.");
+
+ ///
+ /// "Song Select"
+ ///
+ public static LocalisableString SongSelectHeader => new TranslatableString(getKey(@"song_select_header"), @"Song Select");
+
+ ///
+ /// "Right mouse drag to absolute scroll"
+ ///
+ public static LocalisableString RightMouseScroll => new TranslatableString(getKey(@"right_mouse_scroll"), @"Right mouse drag to absolute scroll");
+
+ ///
+ /// "Show converted beatmaps"
+ ///
+ public static LocalisableString ShowConvertedBeatmaps => new TranslatableString(getKey(@"show_converted_beatmaps"), @"Show converted beatmaps");
+
+ ///
+ /// "Display beatmaps from"
+ ///
+ public static LocalisableString StarsMinimum => new TranslatableString(getKey(@"stars_minimum"), @"Display beatmaps from");
+
+ ///
+ /// "up to"
+ ///
+ public static LocalisableString StarsMaximum => new TranslatableString(getKey(@"stars_maximum"), @"up to");
+
+ ///
+ /// "Random selection algorithm"
+ ///
+ public static LocalisableString RandomSelectionAlgorithm => new TranslatableString(getKey(@"random_selection_algorithm"), @"Random selection algorithm");
+
+ ///
+ /// "no limit"
+ ///
+ public static LocalisableString NoLimit => new TranslatableString(getKey(@"no_limit"), @"no limit");
+
+ private static string getKey(string key) => $"{prefix}:{key}";
+ }
+}
diff --git a/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs b/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs
index 95a5d0acbd..b24669e6d5 100644
--- a/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs
+++ b/osu.Game/Online/API/Requests/MarkChannelAsReadRequest.cs
@@ -9,16 +9,16 @@ namespace osu.Game.Online.API.Requests
{
public class MarkChannelAsReadRequest : APIRequest
{
- private readonly Channel channel;
- private readonly Message message;
+ public readonly Channel Channel;
+ public readonly Message Message;
public MarkChannelAsReadRequest(Channel channel, Message message)
{
- this.channel = channel;
- this.message = message;
+ Channel = channel;
+ Message = message;
}
- protected override string Target => $"chat/channels/{channel.Id}/mark-as-read/{message.Id}";
+ protected override string Target => $"chat/channels/{Channel.Id}/mark-as-read/{Message.Id}";
protected override WebRequest CreateWebRequest()
{
diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs
index 3136a3960d..1937019ef6 100644
--- a/osu.Game/Online/Chat/ChannelManager.cs
+++ b/osu.Game/Online/Chat/ChannelManager.cs
@@ -553,7 +553,7 @@ namespace osu.Game.Online.Chat
if (channel.LastMessageId == channel.LastReadId)
return;
- var message = channel.Messages.LastOrDefault();
+ var message = channel.Messages.FindLast(msg => !(msg is LocalMessage));
if (message == null)
return;
diff --git a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs
index 064065ab00..8f16d22c4c 100644
--- a/osu.Game/Online/Multiplayer/IMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/IMultiplayerClient.cs
@@ -31,6 +31,15 @@ namespace osu.Game.Online.Multiplayer
/// The user.
Task UserLeft(MultiplayerRoomUser user);
+ ///
+ /// Signals that a user has been kicked from the room.
+ ///
+ ///
+ /// This will also be sent to the user that was kicked.
+ ///
+ /// The user.
+ Task UserKicked(MultiplayerRoomUser user);
+
///
/// Signal that the host of the room has changed.
///
diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
index b26c4d8201..da637c229f 100644
--- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
+++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs
@@ -27,6 +27,14 @@ namespace osu.Game.Online.Multiplayer
/// If the user is not in a room.
Task TransferHost(int userId);
+ ///
+ /// As the host, kick another user from the room.
+ ///
+ /// The user to kick..
+ /// A user other than the current host is attempting to kick a user.
+ /// If the user is not in a room.
+ Task KickUser(int userId);
+
///
/// As the host, update the settings of the currently joined room.
///
diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
index dafc737ba2..2a0635c98c 100644
--- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs
@@ -293,6 +293,8 @@ namespace osu.Game.Online.Multiplayer
public abstract Task TransferHost(int userId);
+ public abstract Task KickUser(int userId);
+
public abstract Task ChangeSettings(MultiplayerRoomSettings settings);
public abstract Task ChangeState(MultiplayerUserState newState);
@@ -387,6 +389,18 @@ namespace osu.Game.Online.Multiplayer
return Task.CompletedTask;
}
+ Task IMultiplayerClient.UserKicked(MultiplayerRoomUser user)
+ {
+ if (LocalUser == null)
+ return Task.CompletedTask;
+
+ if (user.Equals(LocalUser))
+ LeaveRoom();
+
+ // TODO: also inform users of the kick operation.
+ return ((IMultiplayerClient)this).UserLeft(user);
+ }
+
Task IMultiplayerClient.HostChanged(int userId)
{
if (Room == null)
diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
index 8b8d10ce4f..c38a648a6a 100644
--- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
+++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs
@@ -50,6 +50,7 @@ namespace osu.Game.Online.Multiplayer
connection.On(nameof(IMultiplayerClient.RoomStateChanged), ((IMultiplayerClient)this).RoomStateChanged);
connection.On(nameof(IMultiplayerClient.UserJoined), ((IMultiplayerClient)this).UserJoined);
connection.On(nameof(IMultiplayerClient.UserLeft), ((IMultiplayerClient)this).UserLeft);
+ connection.On(nameof(IMultiplayerClient.UserKicked), ((IMultiplayerClient)this).UserKicked);
connection.On(nameof(IMultiplayerClient.HostChanged), ((IMultiplayerClient)this).HostChanged);
connection.On(nameof(IMultiplayerClient.SettingsChanged), ((IMultiplayerClient)this).SettingsChanged);
connection.On(nameof(IMultiplayerClient.UserStateChanged), ((IMultiplayerClient)this).UserStateChanged);
@@ -91,6 +92,14 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.TransferHost), userId);
}
+ public override Task KickUser(int userId)
+ {
+ if (!IsConnected.Value)
+ return Task.CompletedTask;
+
+ return connection.InvokeAsync(nameof(IMultiplayerServer.KickUser), userId);
+ }
+
public override Task ChangeSettings(MultiplayerRoomSettings settings)
{
if (!IsConnected.Value)
diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs
index c852f86f6b..01f3ae368b 100644
--- a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs
+++ b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
{
public class RoomStatusEnded : RoomStatus
{
- public override string Message => @"Ended";
+ public override string Message => "Ended";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker;
}
}
diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs
index 4f7f0d6f5d..686d4f4033 100644
--- a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs
+++ b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
{
public class RoomStatusOpen : RoomStatus
{
- public override string Message => @"Welcoming Players";
+ public override string Message => "Open";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
}
}
diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs
index f04f1b23af..83f1acf52a 100644
--- a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs
+++ b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs
@@ -8,7 +8,7 @@ namespace osu.Game.Online.Rooms.RoomStatuses
{
public class RoomStatusPlaying : RoomStatus
{
- public override string Message => @"Now Playing";
+ public override string Message => "Playing";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
}
}
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 3cfa2cc755..fb682e0909 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -64,6 +64,11 @@ namespace osu.Game
///
public class OsuGame : OsuGameBase, IKeyBindingHandler
{
+ ///
+ /// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
+ ///
+ protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f;
+
public Toolbar Toolbar;
private ChatOverlay chatOverlay;
@@ -71,7 +76,7 @@ namespace osu.Game
private ChannelManager channelManager;
[NotNull]
- private readonly NotificationOverlay notifications = new NotificationOverlay();
+ protected readonly NotificationOverlay Notifications = new NotificationOverlay();
private BeatmapListingOverlay beatmapListing;
@@ -97,7 +102,7 @@ namespace osu.Game
private ScalingContainer screenContainer;
- private Container screenOffsetContainer;
+ protected Container ScreenOffsetContainer { get; private set; }
[Resolved]
private FrameworkConfigManager frameworkConfig { get; set; }
@@ -312,7 +317,7 @@ namespace osu.Game
case LinkAction.OpenEditorTimestamp:
case LinkAction.JoinMultiplayerMatch:
case LinkAction.Spectate:
- waitForReady(() => notifications, _ => notifications.Post(new SimpleNotification
+ waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification
{
Text = @"This link type is not yet supported!",
Icon = FontAwesome.Solid.LifeRing,
@@ -611,12 +616,12 @@ namespace osu.Game
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
// todo: all archive managers should be able to be looped here.
- SkinManager.PostNotification = n => notifications.Post(n);
+ SkinManager.PostNotification = n => Notifications.Post(n);
- BeatmapManager.PostNotification = n => notifications.Post(n);
+ BeatmapManager.PostNotification = n => Notifications.Post(n);
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
- ScoreManager.PostNotification = n => notifications.Post(n);
+ ScoreManager.PostNotification = n => Notifications.Post(n);
ScoreManager.PresentImport = items => PresentScore(items.First());
// make config aware of how to lookup skins for on-screen display purposes.
@@ -655,7 +660,7 @@ namespace osu.Game
ActionRequested = action => volume.Adjust(action),
ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise),
},
- screenOffsetContainer = new Container
+ ScreenOffsetContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
@@ -724,7 +729,7 @@ namespace osu.Game
loadComponentSingleFile(onScreenDisplay, Add, true);
- loadComponentSingleFile(notifications.With(d =>
+ loadComponentSingleFile(Notifications.With(d =>
{
d.GetToolbarHeight = () => ToolbarOffset;
d.Anchor = Anchor.TopRight;
@@ -733,7 +738,7 @@ namespace osu.Game
loadComponentSingleFile(new CollectionManager(Storage)
{
- PostNotification = n => notifications.Post(n),
+ PostNotification = n => Notifications.Post(n),
}, Add, true);
loadComponentSingleFile(stableImportManager, Add);
@@ -785,7 +790,7 @@ namespace osu.Game
Add(new MusicKeyBindingHandler());
// side overlays which cancel each other.
- var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications };
+ var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications };
foreach (var overlay in singleDisplaySideOverlays)
{
@@ -828,21 +833,6 @@ namespace osu.Game
{
if (mode.NewValue != OverlayActivation.All) CloseAllOverlays();
};
-
- void updateScreenOffset()
- {
- float offset = 0;
-
- if (Settings.State.Value == Visibility.Visible)
- offset += Toolbar.HEIGHT / 2;
- if (notifications.State.Value == Visibility.Visible)
- offset -= Toolbar.HEIGHT / 2;
-
- screenOffsetContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint);
- }
-
- Settings.State.ValueChanged += _ => updateScreenOffset();
- notifications.State.ValueChanged += _ => updateScreenOffset();
}
private void showOverlayAboveOthers(OverlayContainer overlay, OverlayContainer[] otherOverlays)
@@ -874,7 +864,7 @@ namespace osu.Game
if (recentLogCount < short_term_display_limit)
{
- Schedule(() => notifications.Post(new SimpleErrorNotification
+ Schedule(() => Notifications.Post(new SimpleErrorNotification
{
Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb,
Text = entry.Message.Truncate(256) + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty),
@@ -882,7 +872,7 @@ namespace osu.Game
}
else if (recentLogCount == short_term_display_limit)
{
- Schedule(() => notifications.Post(new SimpleNotification
+ Schedule(() => Notifications.Post(new SimpleNotification
{
Icon = FontAwesome.Solid.EllipsisH,
Text = "Subsequent messages have been logged. Click to view log files.",
@@ -1023,9 +1013,18 @@ namespace osu.Game
{
base.UpdateAfterChildren();
- screenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
+ ScreenOffsetContainer.Padding = new MarginPadding { Top = ToolbarOffset };
overlayContent.Padding = new MarginPadding { Top = ToolbarOffset };
+ var horizontalOffset = 0f;
+
+ if (Settings.IsLoaded && Settings.IsPresent)
+ horizontalOffset += ToLocalSpace(Settings.ScreenSpaceDrawQuad.TopRight).X * SIDE_OVERLAY_OFFSET_RATIO;
+ if (Notifications.IsLoaded && Notifications.IsPresent)
+ horizontalOffset += (ToLocalSpace(Notifications.ScreenSpaceDrawQuad.TopLeft).X - DrawWidth) * SIDE_OVERLAY_OFFSET_RATIO;
+
+ ScreenOffsetContainer.X = horizontalOffset;
+
MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false;
}
diff --git a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
index 4bbc3569fe..3aa9aa5ca5 100644
--- a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
+++ b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs
@@ -139,19 +139,24 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
LoadComponentAsync(Preview = previewTrackManager.Get(beatmapSet), preview =>
{
- // beatmapset may have changed.
- if (Preview != preview)
- return;
+ // Make sure that we schedule to after the next audio frame to fix crashes in single-threaded execution.
+ // See: https://github.com/ppy/osu-framework/issues/4692
+ Schedule(() =>
+ {
+ // beatmapset may have changed.
+ if (Preview != preview)
+ return;
- AddInternal(preview);
- loading = false;
- // make sure that the update of value of Playing (and the ensuing value change callbacks)
- // are marshaled back to the update thread.
- preview.Stopped += () => Schedule(() => playing.Value = false);
+ AddInternal(preview);
+ loading = false;
+ // make sure that the update of value of Playing (and the ensuing value change callbacks)
+ // are marshaled back to the update thread.
+ preview.Stopped += () => Schedule(() => playing.Value = false);
- // user may have changed their mind.
- if (playing.Value)
- attemptStart();
+ // user may have changed their mind.
+ if (playing.Value)
+ attemptStart();
+ });
});
}
else
diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
index 3f1034759e..757698e1aa 100644
--- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs
+++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs
@@ -78,10 +78,10 @@ namespace osu.Game.Overlays.BeatmapSet
Direction = FillDirection.Horizontal,
Children = new[]
{
- length = new Statistic(FontAwesome.Regular.Clock, "Length") { Width = 0.25f },
- bpm = new Statistic(FontAwesome.Regular.Circle, "BPM") { Width = 0.25f },
- circleCount = new Statistic(FontAwesome.Regular.Circle, "Circle Count") { Width = 0.25f },
- sliderCount = new Statistic(FontAwesome.Regular.Circle, "Slider Count") { Width = 0.25f },
+ length = new Statistic(BeatmapStatisticsIconType.Length, "Length") { Width = 0.25f },
+ bpm = new Statistic(BeatmapStatisticsIconType.Bpm, "BPM") { Width = 0.25f },
+ circleCount = new Statistic(BeatmapStatisticsIconType.Circles, "Circle Count") { Width = 0.25f },
+ sliderCount = new Statistic(BeatmapStatisticsIconType.Sliders, "Slider Count") { Width = 0.25f },
},
};
}
@@ -104,7 +104,7 @@ namespace osu.Game.Overlays.BeatmapSet
set => this.value.Text = value;
}
- public Statistic(IconUsage icon, string name)
+ public Statistic(BeatmapStatisticsIconType icon, string name)
{
TooltipText = name;
RelativeSizeAxes = Axes.X;
@@ -133,8 +133,16 @@ namespace osu.Game.Overlays.BeatmapSet
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
- Icon = icon,
- Size = new Vector2(12),
+ Icon = FontAwesome.Regular.Circle,
+ Size = new Vector2(10),
+ Rotation = 0,
+ Colour = Color4Extensions.FromHex(@"f7dd55"),
+ },
+ new BeatmapStatisticIcon(icon)
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.Centre,
+ Size = new Vector2(10),
Colour = Color4Extensions.FromHex(@"f7dd55"),
Scale = new Vector2(0.8f),
},
diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs
index 8b89d63aab..93486274fc 100644
--- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs
+++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs
@@ -71,6 +71,17 @@ namespace osu.Game.Overlays.Changelog
Colour = colourProvider.Background6,
Margin = new MarginPadding { Top = 30 },
},
+ new ChangelogSupporterPromo
+ {
+ Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 2,
+ Colour = colourProvider.Background6,
+ Alpha = api.LocalUser.Value.IsSupporter ? 0 : 1,
+ },
comments = new CommentsContainer()
};
diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
new file mode 100644
index 0000000000..508c8399b6
--- /dev/null
+++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs
@@ -0,0 +1,200 @@
+// 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.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online.Chat;
+using osu.Game.Resources.Localisation.Web;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Overlays.Changelog
+{
+ public class ChangelogSupporterPromo : CompositeDrawable
+ {
+ private const float image_container_width = 164;
+ private const float heart_size = 75;
+
+ private readonly FillFlowContainer textContainer;
+ private readonly Container imageContainer;
+
+ public ChangelogSupporterPromo()
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ Padding = new MarginPadding
+ {
+ Vertical = 20,
+ Horizontal = 50,
+ };
+
+ InternalChildren = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Masking = true,
+ CornerRadius = 6,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Black.Opacity(0.25f),
+ Offset = new Vector2(0, 1),
+ Radius = 3,
+ },
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black.Opacity(0.3f),
+ },
+ new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = 200,
+ Padding = new MarginPadding { Horizontal = 75 },
+ Children = new Drawable[]
+ {
+ textContainer = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Padding = new MarginPadding { Right = 50 + image_container_width },
+ },
+ imageContainer = new Container
+ {
+ RelativeSizeAxes = Axes.Y,
+ Width = image_container_width,
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ }
+ }
+ },
+ }
+ },
+ };
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colour, TextureStore textures)
+ {
+ SupporterPromoLinkFlowContainer supportLinkText;
+ textContainer.Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Text = ChangelogStrings.SupportHeading,
+ Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
+ Margin = new MarginPadding { Bottom = 20 },
+ },
+ supportLinkText = new SupporterPromoLinkFlowContainer(t =>
+ {
+ t.Font = t.Font.With(size: 14);
+ t.Colour = colour.PinkLighter;
+ })
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ },
+ new OsuTextFlowContainer(t =>
+ {
+ t.Font = t.Font.With(size: 12);
+ t.Colour = colour.PinkLighter;
+ })
+ {
+ Text = ChangelogStrings.SupportText2.ToString(),
+ Margin = new MarginPadding { Top = 10 },
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ }
+ };
+
+ supportLinkText.AddText("Support further development of osu! and ");
+ supportLinkText.AddLink("become and osu!supporter", "https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
+ supportLinkText.AddText(" today!");
+
+ imageContainer.Children = new Drawable[]
+ {
+ new Sprite
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Margin = new MarginPadding { Bottom = 28 },
+ RelativeSizeAxes = Axes.Both,
+ FillMode = FillMode.Fill,
+ Texture = textures.Get(@"Online/supporter-pippi"),
+ },
+ new Container
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Size = new Vector2(heart_size),
+ Margin = new MarginPadding { Top = 70 },
+ Masking = true,
+ EdgeEffect = new EdgeEffectParameters
+ {
+ Type = EdgeEffectType.Shadow,
+ Colour = colour.Pink,
+ Radius = 10,
+ Roundness = heart_size / 2,
+ },
+ Child = new Sprite
+ {
+ Size = new Vector2(heart_size),
+ Texture = textures.Get(@"Online/supporter-heart"),
+ },
+ },
+ };
+ }
+
+ private class SupporterPromoLinkFlowContainer : LinkFlowContainer
+ {
+ public SupporterPromoLinkFlowContainer(Action defaultCreationParameters)
+ : base(defaultCreationParameters)
+ {
+ }
+
+ public new void AddLink(string text, string url, Action creationParameters) =>
+ AddInternal(new SupporterPromoLinkCompiler(AddText(text, creationParameters)) { Url = url });
+
+ private class SupporterPromoLinkCompiler : DrawableLinkCompiler
+ {
+ [Resolved(CanBeNull = true)]
+ private OsuGame game { get; set; }
+
+ public string Url;
+
+ public SupporterPromoLinkCompiler(IEnumerable parts)
+ : base(parts)
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colour)
+ {
+ TooltipText = Url;
+ Action = () => game?.HandleLink(Url);
+ IdleColour = colour.PinkDark;
+ HoverColour = Color4.White;
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs
index 513fabf52a..fe8d6f0178 100644
--- a/osu.Game/Overlays/Comments/CommentsContainer.cs
+++ b/osu.Game/Overlays/Comments/CommentsContainer.cs
@@ -14,6 +14,9 @@ using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Threading;
using osu.Game.Users;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Comments
{
@@ -147,7 +150,7 @@ namespace osu.Game.Overlays.Comments
private void refetchComments()
{
- clearComments();
+ ClearComments();
getComments();
}
@@ -160,50 +163,125 @@ namespace osu.Game.Overlays.Comments
loadCancellation?.Cancel();
scheduledCommentsLoad?.Cancel();
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
- request.Success += res => scheduledCommentsLoad = Schedule(() => onSuccess(res));
+ request.Success += res => scheduledCommentsLoad = Schedule(() => OnSuccess(res));
api.PerformAsync(request);
}
- private void clearComments()
+ protected void ClearComments()
{
currentPage = 1;
deletedCommentsCounter.Count.Value = 0;
moreButton.Show();
moreButton.IsLoading = true;
content.Clear();
+ CommentDictionary.Clear();
}
- private void onSuccess(CommentBundle response)
+ protected readonly Dictionary CommentDictionary = new Dictionary();
+
+ protected void OnSuccess(CommentBundle response)
{
- loadCancellation = new CancellationTokenSource();
+ commentCounter.Current.Value = response.Total;
- LoadComponentAsync(new CommentsPage(response)
+ if (!response.Comments.Any())
{
- ShowDeleted = { BindTarget = ShowDeleted },
- Sort = { BindTarget = Sort },
- Type = { BindTarget = type },
- CommentableId = { BindTarget = id }
- }, loaded =>
+ content.Add(new NoCommentsPlaceholder());
+ moreButton.Hide();
+ return;
+ }
+
+ AppendComments(response);
+ }
+
+ ///
+ /// Appends retrieved comments to the subtree rooted of comments in this page.
+ ///
+ /// The bundle of comments to add.
+ protected void AppendComments([NotNull] CommentBundle bundle)
+ {
+ var topLevelComments = new List();
+ var orphaned = new List();
+
+ foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
{
- content.Add(loaded);
+ // Exclude possible duplicated comments.
+ if (CommentDictionary.ContainsKey(comment.Id))
+ continue;
- deletedCommentsCounter.Count.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
+ addNewComment(comment);
+ }
- if (response.HasMore)
+ // Comments whose parents were seen later than themselves can now be added.
+ foreach (var o in orphaned)
+ addNewComment(o);
+
+ if (topLevelComments.Any())
+ {
+ LoadComponentsAsync(topLevelComments, loaded =>
{
- int loadedTopLevelComments = 0;
- content.Children.OfType().ForEach(p => loadedTopLevelComments += p.Children.OfType().Count());
+ content.AddRange(loaded);
- moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
- moreButton.IsLoading = false;
+ deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
+
+ if (bundle.HasMore)
+ {
+ int loadedTopLevelComments = 0;
+ content.Children.OfType().ForEach(p => loadedTopLevelComments++);
+
+ moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments;
+ moreButton.IsLoading = false;
+ }
+ else
+ {
+ moreButton.Hide();
+ }
+ }, (loadCancellation = new CancellationTokenSource()).Token);
+ }
+
+ void addNewComment(Comment comment)
+ {
+ var drawableComment = getDrawableComment(comment);
+
+ if (comment.ParentId == null)
+ {
+ // Comments that have no parent are added as top-level comments to the flow.
+ topLevelComments.Add(drawableComment);
+ }
+ else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
+ {
+ // The comment's parent has already been seen, so the parent<-> child links can be added.
+ comment.ParentComment = parentDrawable.Comment;
+ parentDrawable.Replies.Add(drawableComment);
}
else
{
- moreButton.Hide();
+ // The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
+ // Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
+ orphaned.Add(comment);
}
+ }
+ }
- commentCounter.Current.Value = response.Total;
- }, loadCancellation.Token);
+ private DrawableComment getDrawableComment(Comment comment)
+ {
+ if (CommentDictionary.TryGetValue(comment.Id, out var existing))
+ return existing;
+
+ return CommentDictionary[comment.Id] = new DrawableComment(comment)
+ {
+ ShowDeleted = { BindTarget = ShowDeleted },
+ Sort = { BindTarget = Sort },
+ RepliesRequested = onCommentRepliesRequested
+ };
+ }
+
+ private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
+ {
+ var req = new GetCommentsRequest(id.Value, type.Value, Sort.Value, page, drawableComment.Comment.Id);
+
+ req.Success += response => Schedule(() => AppendComments(response));
+
+ api.PerformAsync(req);
}
protected override void Dispose(bool isDisposing)
@@ -212,5 +290,30 @@ namespace osu.Game.Overlays.Comments
loadCancellation?.Cancel();
base.Dispose(isDisposing);
}
+
+ private class NoCommentsPlaceholder : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ Height = 80;
+ RelativeSizeAxes = Axes.X;
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background4
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Margin = new MarginPadding { Left = 50 },
+ Text = @"No comments yet."
+ }
+ });
+ }
+ }
}
}
diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs
deleted file mode 100644
index 9b146b0a7d..0000000000
--- a/osu.Game/Overlays/Comments/CommentsPage.cs
+++ /dev/null
@@ -1,161 +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.Graphics.Containers;
-using osu.Framework.Graphics;
-using osu.Framework.Bindables;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics.Sprites;
-using System.Linq;
-using osu.Game.Online.API.Requests;
-using osu.Game.Online.API;
-using System.Collections.Generic;
-using JetBrains.Annotations;
-
-namespace osu.Game.Overlays.Comments
-{
- public class CommentsPage : CompositeDrawable
- {
- public readonly BindableBool ShowDeleted = new BindableBool();
- public readonly Bindable Sort = new Bindable();
- public readonly Bindable Type = new Bindable();
- public readonly BindableLong CommentableId = new BindableLong();
-
- [Resolved]
- private IAPIProvider api { get; set; }
-
- private readonly CommentBundle commentBundle;
- private FillFlowContainer flow;
-
- public CommentsPage(CommentBundle commentBundle)
- {
- this.commentBundle = commentBundle;
- }
-
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
-
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background5
- },
- flow = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- }
- });
-
- if (!commentBundle.Comments.Any())
- {
- flow.Add(new NoCommentsPlaceholder());
- return;
- }
-
- AppendComments(commentBundle);
- }
-
- private DrawableComment getDrawableComment(Comment comment)
- {
- if (CommentDictionary.TryGetValue(comment.Id, out var existing))
- return existing;
-
- return CommentDictionary[comment.Id] = new DrawableComment(comment)
- {
- ShowDeleted = { BindTarget = ShowDeleted },
- Sort = { BindTarget = Sort },
- RepliesRequested = onCommentRepliesRequested
- };
- }
-
- private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
- {
- var request = new GetCommentsRequest(CommentableId.Value, Type.Value, Sort.Value, page, drawableComment.Comment.Id);
-
- request.Success += response => Schedule(() => AppendComments(response));
-
- api.PerformAsync(request);
- }
-
- protected readonly Dictionary CommentDictionary = new Dictionary();
-
- ///
- /// Appends retrieved comments to the subtree rooted of comments in this page.
- ///
- /// The bundle of comments to add.
- protected void AppendComments([NotNull] CommentBundle bundle)
- {
- var orphaned = new List();
-
- foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
- {
- // Exclude possible duplicated comments.
- if (CommentDictionary.ContainsKey(comment.Id))
- continue;
-
- addNewComment(comment);
- }
-
- // Comments whose parents were seen later than themselves can now be added.
- foreach (var o in orphaned)
- addNewComment(o);
-
- void addNewComment(Comment comment)
- {
- var drawableComment = getDrawableComment(comment);
-
- if (comment.ParentId == null)
- {
- // Comments that have no parent are added as top-level comments to the flow.
- flow.Add(drawableComment);
- }
- else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
- {
- // The comment's parent has already been seen, so the parent<-> child links can be added.
- comment.ParentComment = parentDrawable.Comment;
- parentDrawable.Replies.Add(drawableComment);
- }
- else
- {
- // The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
- // Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
- orphaned.Add(comment);
- }
- }
- }
-
- private class NoCommentsPlaceholder : CompositeDrawable
- {
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- Height = 80;
- RelativeSizeAxes = Axes.X;
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background4
- },
- new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Margin = new MarginPadding { Left = 50 },
- Text = @"No comments yet."
- }
- });
- }
- }
- }
-}
diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs
index b26e17b34c..e3956089c2 100644
--- a/osu.Game/Overlays/NotificationOverlay.cs
+++ b/osu.Game/Overlays/NotificationOverlay.cs
@@ -24,7 +24,7 @@ namespace osu.Game.Overlays
public LocalisableString Title => NotificationsStrings.HeaderTitle;
public LocalisableString Description => NotificationsStrings.HeaderDescription;
- private const float width = 320;
+ public const float WIDTH = 320;
public const float TRANSITION_LENGTH = 600;
@@ -38,7 +38,8 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader]
private void load()
{
- Width = width;
+ X = WIDTH;
+ Width = WIDTH;
RelativeSizeAxes = Axes.Y;
Children = new Drawable[]
@@ -152,7 +153,7 @@ namespace osu.Game.Overlays
markAllRead();
- this.MoveToX(width, TRANSITION_LENGTH, Easing.OutQuint);
+ this.MoveToX(WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
}
diff --git a/osu.Game/Overlays/OverlayColourProvider.cs b/osu.Game/Overlays/OverlayColourProvider.cs
index 008e7696e1..e7b3e6d873 100644
--- a/osu.Game/Overlays/OverlayColourProvider.cs
+++ b/osu.Game/Overlays/OverlayColourProvider.cs
@@ -18,6 +18,7 @@ namespace osu.Game.Overlays
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
+ public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum);
public OverlayColourProvider(OverlayColourScheme colourScheme)
{
@@ -80,6 +81,9 @@ namespace osu.Game.Overlays
case OverlayColourScheme.Blue:
return 200 / 360f;
+
+ case OverlayColourScheme.Plum:
+ return 320 / 360f;
}
}
}
@@ -92,6 +96,7 @@ namespace osu.Game.Overlays
Lime,
Green,
Purple,
- Blue
+ Blue,
+ Plum,
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
index d64f176468..501f1b86b8 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs
@@ -8,12 +8,13 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class AudioDevicesSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Devices";
+ protected override LocalisableString Header => AudioSettingsStrings.AudioDevicesHeader;
[Resolved]
private AudioManager audio { get; set; }
@@ -78,7 +79,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
private class AudioDeviceDropdownControl : DropdownControl
{
protected override LocalisableString GenerateItemText(string item)
- => string.IsNullOrEmpty(item) ? "Default" : base.GenerateItemText(item);
+ => string.IsNullOrEmpty(item) ? CommonStrings.Default : base.GenerateItemText(item);
}
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
index 7f2e377c83..9345d3fcc7 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs
@@ -1,17 +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 System.Collections.Generic;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class OffsetSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Offset Adjustment";
+ protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader;
+
+ public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "universal", "uo", "timing" });
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -20,13 +25,13 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
new SettingsSlider
{
- LabelText = "Audio offset",
+ LabelText = AudioSettingsStrings.AudioOffset,
Current = config.GetBindable(OsuSetting.AudioOffset),
KeyboardStep = 1f
},
new SettingsButton
{
- Text = "Offset wizard"
+ Text = AudioSettingsStrings.OffsetWizard
}
};
}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
index 8f88b03471..00c1cb8f43 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Audio;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Audio
{
public class VolumeSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Volume";
+ protected override LocalisableString Header => AudioSettingsStrings.VolumeHeader;
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuConfigManager config)
@@ -20,28 +21,28 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
new SettingsSlider
{
- LabelText = "Master",
+ LabelText = AudioSettingsStrings.MasterVolume,
Current = audio.Volume,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Master (window inactive)",
+ LabelText = AudioSettingsStrings.MasterVolumeInactive,
Current = config.GetBindable(OsuSetting.VolumeInactive),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Effect",
+ LabelText = AudioSettingsStrings.EffectVolume,
Current = audio.VolumeSample,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Music",
+ LabelText = AudioSettingsStrings.MusicVolume,
Current = audio.VolumeTrack,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs
index 7072d8e63d..694da0529a 100644
--- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs
@@ -3,15 +3,17 @@
using System.Collections.Generic;
using System.Linq;
+using osu.Framework.Localisation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Audio;
namespace osu.Game.Overlays.Settings.Sections
{
public class AudioSection : SettingsSection
{
- public override string Header => "Audio";
+ public override LocalisableString Header => AudioSettingsStrings.AudioSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
index 2b868cab85..25e20911b8 100644
--- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs
@@ -6,13 +6,14 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Screens;
+using osu.Game.Localisation;
using osu.Game.Screens.Import;
namespace osu.Game.Overlays.Settings.Sections.Debug
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => DebugSettingsStrings.GeneralHeader;
[BackgroundDependencyLoader(true)]
private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, OsuGame game)
@@ -21,18 +22,18 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{
new SettingsCheckbox
{
- LabelText = "Show log overlay",
+ LabelText = DebugSettingsStrings.ShowLogOverlay,
Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay)
},
new SettingsCheckbox
{
- LabelText = "Bypass front-to-back render pass",
+ LabelText = DebugSettingsStrings.BypassFrontToBackPass,
Current = config.GetBindable(DebugSetting.BypassFrontToBackPass)
}
};
Add(new SettingsButton
{
- Text = "Import files",
+ Text = DebugSettingsStrings.ImportFiles,
Action = () => game?.PerformFromScreen(menu => menu.Push(new FileImportScreen()))
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
index bf7fb351c0..07fb0aca5a 100644
--- a/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Debug/MemorySettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Debug
{
public class MemorySettings : SettingsSubsection
{
- protected override LocalisableString Header => "Memory";
+ protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader;
[BackgroundDependencyLoader]
private void load(FrameworkDebugConfigManager config, GameHost host)
@@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.Debug
{
new SettingsButton
{
- Text = "Clear all caches",
+ Text = DebugSettingsStrings.ClearAllCaches,
Action = host.Collect
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs
index 44d4088972..aa85ec920c 100644
--- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Debug;
namespace osu.Game.Overlays.Settings.Sections
{
public class DebugSection : SettingsSection
{
- public override string Header => "Debug";
+ public override LocalisableString Header => DebugSettingsStrings.DebugSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
index 353292606f..3a0265e453 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs
@@ -6,13 +6,14 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => GameplaySettingsStrings.GeneralHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -21,62 +22,62 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
new SettingsSlider
{
- LabelText = "Background dim",
+ LabelText = GameplaySettingsStrings.BackgroundDim,
Current = config.GetBindable(OsuSetting.DimLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Background blur",
+ LabelText = GameplaySettingsStrings.BackgroundBlur,
Current = config.GetBindable(OsuSetting.BlurLevel),
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsCheckbox
{
- LabelText = "Lighten playfield during breaks",
+ LabelText = GameplaySettingsStrings.LightenDuringBreaks,
Current = config.GetBindable(OsuSetting.LightenDuringBreaks)
},
new SettingsEnumDropdown
{
- LabelText = "HUD overlay visibility mode",
+ LabelText = GameplaySettingsStrings.HUDVisibilityMode,
Current = config.GetBindable(OsuSetting.HUDVisibilityMode)
},
new SettingsCheckbox
{
- LabelText = "Show difficulty graph on progress bar",
+ LabelText = GameplaySettingsStrings.ShowDifficultyGraph,
Current = config.GetBindable(OsuSetting.ShowProgressGraph)
},
new SettingsCheckbox
{
- LabelText = "Show health display even when you can't fail",
+ LabelText = GameplaySettingsStrings.ShowHealthDisplayWhenCantFail,
Current = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail),
Keywords = new[] { "hp", "bar" }
},
new SettingsCheckbox
{
- LabelText = "Fade playfield to red when health is low",
+ LabelText = GameplaySettingsStrings.FadePlayfieldWhenHealthLow,
Current = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow),
},
new SettingsCheckbox
{
- LabelText = "Always show key overlay",
+ LabelText = GameplaySettingsStrings.AlwaysShowKeyOverlay,
Current = config.GetBindable(OsuSetting.KeyOverlay)
},
new SettingsCheckbox
{
- LabelText = "Positional hitsounds",
+ LabelText = GameplaySettingsStrings.PositionalHitsounds,
Current = config.GetBindable(OsuSetting.PositionalHitSounds)
},
new SettingsCheckbox
{
- LabelText = "Always play first combo break sound",
+ LabelText = GameplaySettingsStrings.AlwaysPlayFirstComboBreak,
Current = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak)
},
new SettingsEnumDropdown
{
- LabelText = "Score display mode",
+ LabelText = GameplaySettingsStrings.ScoreDisplayMode,
Current = config.GetBindable(OsuSetting.ScoreDisplayMode),
Keywords = new[] { "scoring" }
},
@@ -86,7 +87,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
Add(new SettingsCheckbox
{
- LabelText = "Disable Windows key during gameplay",
+ LabelText = GameplaySettingsStrings.DisableWinKey,
Current = config.GetBindable(OsuSetting.GameplayDisableWinKey)
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
index ec9ddde2da..dfa060e8d5 100644
--- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs
@@ -6,12 +6,13 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
public class ModsSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Mods";
+ protected override LocalisableString Header => GameplaySettingsStrings.ModsHeader;
public override IEnumerable FilterTerms => base.FilterTerms.Concat(new[] { "mod" });
@@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
{
new SettingsCheckbox
{
- LabelText = "Increase visibility of first object when visual impairment mods are enabled",
+ LabelText = GameplaySettingsStrings.IncreaseFirstObjectVisibility,
Current = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility),
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
index acb94a6a01..42d9d48d73 100644
--- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs
@@ -9,12 +9,14 @@ using osu.Game.Rulesets;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Logging;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections
{
public class GameplaySection : SettingsSection
{
- public override string Header => "Gameplay";
+ public override LocalisableString Header => GameplaySettingsStrings.GameplaySectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
index c6c752e2fd..200618c469 100644
--- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs
@@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
private SettingsDropdown languageSelection;
private Bindable frameworkLocale;
- protected override LocalisableString Header => "Language";
+ protected override LocalisableString Header => GeneralSettingsStrings.LanguageHeader;
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig)
@@ -27,11 +27,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
languageSelection = new SettingsEnumDropdown
{
- LabelText = "Language",
+ LabelText = GeneralSettingsStrings.LanguageDropdown,
},
new SettingsCheckbox
{
- LabelText = "Prefer metadata in original language",
+ LabelText = GeneralSettingsStrings.PreferOriginalMetadataLanguage,
Current = frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode)
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
index dd20e1d7ef..aa37748653 100644
--- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs
@@ -9,6 +9,7 @@ using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osu.Game.Updater;
@@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
[Resolved(CanBeNull = true)]
private UpdateManager updateManager { get; set; }
- protected override LocalisableString Header => "Updates";
+ protected override LocalisableString Header => GeneralSettingsStrings.UpdateHeader;
private SettingsButton checkForUpdatesButton;
@@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(new SettingsEnumDropdown
{
- LabelText = "Release stream",
+ LabelText = GeneralSettingsStrings.ReleaseStream,
Current = config.GetBindable(OsuSetting.ReleaseStream),
});
@@ -40,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(checkForUpdatesButton = new SettingsButton
{
- Text = "Check for updates",
+ Text = GeneralSettingsStrings.CheckUpdate,
Action = () =>
{
checkForUpdatesButton.Enabled.Value = false;
@@ -65,13 +66,13 @@ namespace osu.Game.Overlays.Settings.Sections.General
{
Add(new SettingsButton
{
- Text = "Open osu! folder",
+ Text = GeneralSettingsStrings.OpenOsuFolder,
Action = storage.OpenInNativeExplorer,
});
Add(new SettingsButton
{
- Text = "Change folder location...",
+ Text = GeneralSettingsStrings.ChangeFolderLocation,
Action = () => game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen()))
});
}
diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
index fefc3fe6a7..87e9f34833 100644
--- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.General;
namespace osu.Game.Overlays.Settings.Sections
{
public class GeneralSection : SettingsSection
{
- public override string Header => "General";
+ public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
index f889cfca0f..20b1d8d801 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class DetailSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Detail Settings";
+ protected override LocalisableString Header => GraphicsSettingsStrings.DetailSettingsHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,22 +20,22 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
new SettingsCheckbox
{
- LabelText = "Storyboard / Video",
+ LabelText = GraphicsSettingsStrings.StoryboardVideo,
Current = config.GetBindable(OsuSetting.ShowStoryboard)
},
new SettingsCheckbox
{
- LabelText = "Hit Lighting",
+ LabelText = GraphicsSettingsStrings.HitLighting,
Current = config.GetBindable(OsuSetting.HitLighting)
},
new SettingsEnumDropdown
{
- LabelText = "Screenshot format",
+ LabelText = GraphicsSettingsStrings.ScreenshotFormat,
Current = config.GetBindable(OsuSetting.ScreenshotFormat)
},
new SettingsCheckbox
{
- LabelText = "Show menu cursor in screenshots",
+ LabelText = GraphicsSettingsStrings.ShowCursorInScreenshots,
Current = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor)
}
};
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
index 91208cb78a..124b3b804c 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs
@@ -16,13 +16,14 @@ using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osuTK.Graphics;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class LayoutSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Layout";
+ protected override LocalisableString Header => GraphicsSettingsStrings.LayoutHeader;
private FillFlowContainer> scalingSettings;
@@ -67,20 +68,20 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
windowModeDropdown = new SettingsDropdown
{
- LabelText = "Screen mode",
+ LabelText = GraphicsSettingsStrings.ScreenMode,
ItemSource = windowModes,
Current = config.GetBindable(FrameworkSetting.WindowMode),
},
resolutionDropdown = new ResolutionSettingsDropdown
{
- LabelText = "Resolution",
+ LabelText = GraphicsSettingsStrings.Resolution,
ShowsDefaultIndicator = false,
ItemSource = resolutions,
Current = sizeFullscreen
},
new SettingsSlider
{
- LabelText = "UI Scaling",
+ LabelText = GraphicsSettingsStrings.UIScaling,
TransferValueOnCommit = true,
Current = osuConfig.GetBindable(OsuSetting.UIScale),
KeyboardStep = 0.01f,
@@ -88,7 +89,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
},
new SettingsEnumDropdown
{
- LabelText = "Screen Scaling",
+ LabelText = GraphicsSettingsStrings.ScreenScaling,
Current = osuConfig.GetBindable(OsuSetting.Scaling),
Keywords = new[] { "scale", "letterbox" },
},
@@ -104,28 +105,28 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
new SettingsSlider
{
- LabelText = "Horizontal position",
+ LabelText = GraphicsSettingsStrings.HorizontalPosition,
Current = scalingPositionX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Vertical position",
+ LabelText = GraphicsSettingsStrings.VerticalPosition,
Current = scalingPositionY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Horizontal scale",
+ LabelText = GraphicsSettingsStrings.HorizontalScale,
Current = scalingSizeX,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
new SettingsSlider
{
- LabelText = "Vertical scale",
+ LabelText = GraphicsSettingsStrings.VerticalScale,
Current = scalingSizeY,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
@@ -145,9 +146,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
{
updateResolutionDropdown();
- const string not_fullscreen_note = "Running without fullscreen mode will increase your input latency!";
-
- windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? not_fullscreen_note : string.Empty;
+ windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default;
}, true);
windowModes.BindCollectionChanged((sender, args) =>
@@ -245,7 +244,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
protected override LocalisableString GenerateItemText(Size item)
{
if (item == new Size(9999, 9999))
- return "Default";
+ return CommonStrings.Default;
return $"{item.Width}x{item.Height}";
}
diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
index 2210c7911e..653f30a018 100644
--- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs
@@ -7,12 +7,13 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Graphics
{
public class RendererSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Renderer";
+ protected override LocalisableString Header => GraphicsSettingsStrings.RendererHeader;
private SettingsEnumDropdown frameLimiterDropdown;
@@ -25,17 +26,17 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
// TODO: this needs to be a custom dropdown at some point
frameLimiterDropdown = new SettingsEnumDropdown
{
- LabelText = "Frame limiter",
+ LabelText = GraphicsSettingsStrings.FrameLimiter,
Current = config.GetBindable(FrameworkSetting.FrameSync)
},
new SettingsEnumDropdown
{
- LabelText = "Threading mode",
+ LabelText = GraphicsSettingsStrings.ThreadingMode,
Current = config.GetBindable(FrameworkSetting.ExecutionMode)
},
new SettingsCheckbox
{
- LabelText = "Show FPS",
+ LabelText = GraphicsSettingsStrings.ShowFPS,
Current = osuConfig.GetBindable(OsuSetting.ShowFpsDisplay)
},
};
@@ -47,9 +48,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
frameLimiterDropdown.Current.BindValueChanged(limit =>
{
- const string unlimited_frames_note = "Using unlimited frame limiter can lead to stutters, bad performance and overheating. It will not improve perceived latency. \"2x refresh rate\" is recommended.";
-
- frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? unlimited_frames_note : string.Empty;
+ frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? GraphicsSettingsStrings.UnlimitedFramesNote : default;
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
index 4ade48031f..fd0718f9f2 100644
--- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Graphics;
namespace osu.Game.Overlays.Settings.Sections
{
public class GraphicsSection : SettingsSection
{
- public override string Header => "Graphics";
+ public override LocalisableString Header => GraphicsSettingsStrings.GraphicsSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
index 9898a50320..3350ff4eaa 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/GlobalKeyBindingsSection.cs
@@ -5,6 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Input
{
@@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Icon = FontAwesome.Solid.Globe
};
- public override string Header => "Global";
+ public override LocalisableString Header => InputSettingsStrings.GlobalKeyBindingHeader;
public GlobalKeyBindingsSection(GlobalActionContainer manager)
{
@@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class SongSelectKeyBindingSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Song Select";
+ protected override LocalisableString Header => InputSettingsStrings.SongSelectSection;
public SongSelectKeyBindingSubsection(GlobalActionContainer manager)
: base(null)
@@ -50,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class InGameKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "In Game";
+ protected override LocalisableString Header => InputSettingsStrings.InGameSection;
public InGameKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
@@ -61,7 +62,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class AudioControlKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Audio";
+ protected override LocalisableString Header => InputSettingsStrings.AudioSection;
public AudioControlKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
@@ -72,7 +73,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private class EditorKeyBindingsSubsection : KeyBindingsSubsection
{
- protected override LocalisableString Header => "Editor";
+ protected override LocalisableString Header => InputSettingsStrings.EditorSection;
public EditorKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
index 7cdc739b7c..67f1bb8d3e 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingPanel.cs
@@ -4,13 +4,14 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.Settings.Sections.Input
{
public class KeyBindingPanel : SettingsSubPanel
{
- protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!");
+ protected override Drawable CreateHeader() => new SettingsHeader(InputSettingsStrings.KeyBindingPanelHeader, InputSettingsStrings.KeyBindingPanelDescription);
[BackgroundDependencyLoader(permitNulls: true)]
private void load(RulesetStore rulesets, GlobalActionContainer global)
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
index 6e018597be..c38c516f21 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingRow.cs
@@ -20,6 +20,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Input.Bindings;
+using osu.Game.Localisation;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@@ -385,7 +386,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public CancelButton()
{
- Text = "Cancel";
+ Text = CommonStrings.Cancel;
Size = new Vector2(80, 20);
}
}
@@ -394,7 +395,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
public ClearButton()
{
- Text = "Clear";
+ Text = CommonStrings.Clear;
Size = new Vector2(80, 20);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
index d65684fd37..ef5ccae1a0 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs
@@ -10,6 +10,7 @@ using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
+using osu.Game.Localisation;
using osuTK;
namespace osu.Game.Overlays.Settings.Sections.Input
@@ -64,7 +65,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
[BackgroundDependencyLoader]
private void load()
{
- Text = "Reset all bindings in section";
+ Text = InputSettingsStrings.ResetSectionButton;
RelativeSizeAxes = Axes.X;
Width = 0.5f;
Anchor = Anchor.TopCentre;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
index 81a4d7eccd..5246051a4a 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/RulesetBindingsSection.cs
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Rulesets;
@@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Icon = OsuIcon.Hot
};
- public override string Header => ruleset.Name;
+ public override LocalisableString Header => ruleset.Name;
private readonly RulesetInfo ruleset;
diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
index c7342c251d..b8b86d9069 100644
--- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.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;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -9,9 +10,11 @@ using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Framework.Threading;
+using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Localisation;
+using osu.Game.Online.Chat;
namespace osu.Game.Overlays.Settings.Sections.Input
{
@@ -19,6 +22,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
private readonly ITabletHandler tabletHandler;
+ private readonly Bindable enabled = new BindableBool(true);
+
private readonly Bindable areaOffset = new Bindable();
private readonly Bindable areaSize = new Bindable();
private readonly IBindable tablet = new Bindable();
@@ -52,7 +57,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private FillFlowContainer mainSettings;
- private OsuSpriteText noTabletMessage;
+ private FillFlowContainer noTabletMessage;
protected override LocalisableString Header => TabletSettingsStrings.Tablet;
@@ -62,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}
[BackgroundDependencyLoader]
- private void load()
+ private void load(OsuColour colours)
{
Children = new Drawable[]
{
@@ -71,14 +76,41 @@ namespace osu.Game.Overlays.Settings.Sections.Input
LabelText = CommonStrings.Enabled,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
- Current = tabletHandler.Enabled
+ Current = enabled,
},
- noTabletMessage = new OsuSpriteText
+ noTabletMessage = new FillFlowContainer
{
- Text = TabletSettingsStrings.NoTabletDetected,
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS }
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Vertical,
+ Padding = new MarginPadding { Horizontal = SettingsPanel.CONTENT_MARGINS },
+ Spacing = new Vector2(5f),
+ Children = new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Text = TabletSettingsStrings.NoTabletDetected,
+ },
+ new SettingsNoticeText(colours)
+ {
+ TextAnchor = Anchor.TopCentre,
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ }.With(t =>
+ {
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
+ {
+ t.NewLine();
+ t.AddText("If your tablet is not detected, please read ");
+ t.AddLink("this FAQ", LinkAction.External, RuntimeInfo.OS == RuntimeInfo.Platform.Windows
+ ? @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Windows-FAQ"
+ : @"https://github.com/OpenTabletDriver/OpenTabletDriver/wiki/Linux-FAQ");
+ t.AddText(" for troubleshooting steps.");
+ }
+ }),
+ }
},
mainSettings = new FillFlowContainer
{
@@ -164,6 +196,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
base.LoadComplete();
+ enabled.BindTo(tabletHandler.Enabled);
+ enabled.BindValueChanged(_ => Scheduler.AddOnce(updateVisibility));
+
rotation.BindTo(tabletHandler.Rotation);
areaOffset.BindTo(tabletHandler.AreaOffset);
@@ -209,7 +244,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
tablet.BindTo(tabletHandler.Tablet);
tablet.BindValueChanged(val =>
{
- Scheduler.AddOnce(toggleVisibility);
+ Scheduler.AddOnce(updateVisibility);
var tab = val.NewValue;
@@ -229,19 +264,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, true);
}
- private void toggleVisibility()
+ private void updateVisibility()
{
- bool tabletFound = tablet.Value != null;
-
- if (!tabletFound)
- {
- mainSettings.Hide();
- noTabletMessage.Show();
- return;
- }
-
- mainSettings.Show();
+ mainSettings.Hide();
noTabletMessage.Hide();
+
+ if (!tabletHandler.Enabled.Value)
+ return;
+
+ if (tablet.Value != null)
+ mainSettings.Show();
+ else
+ noTabletMessage.Show();
}
private void applyAspectRatio(BindableNumber sizeChanged)
diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs
index 366f39388a..d282ba5318 100644
--- a/osu.Game/Overlays/Settings/Sections/InputSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs
@@ -11,6 +11,7 @@ using osu.Framework.Input.Handlers.Mouse;
using osu.Framework.Input.Handlers.Tablet;
using osu.Framework.Localisation;
using osu.Framework.Platform;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Input;
namespace osu.Game.Overlays.Settings.Sections
@@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
private readonly KeyBindingPanel keyConfig;
- public override string Header => "Input";
+ public override LocalisableString Header => InputSettingsStrings.InputSectionHeader;
[Resolved]
private GameHost host { get; set; }
@@ -95,7 +96,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
new SettingsCheckbox
{
- LabelText = "Enabled",
+ LabelText = CommonStrings.Enabled,
Current = handler.Enabled
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
index 5392ba5d93..e509cac2f1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs
@@ -14,6 +14,7 @@ using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Graphics.Containers;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
@@ -104,7 +105,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Origin = Anchor.Centre,
Width = 300,
Margin = new MarginPadding(10),
- Text = "Select directory",
+ Text = MaintenanceSettingsStrings.SelectDirectory,
Action = () => OnSelection(directorySelector.CurrentPath.Value)
},
}
diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
index b9a408b1f8..803c8332c1 100644
--- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs
@@ -11,6 +11,7 @@ using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Scoring;
using osu.Game.Skinning;
@@ -37,7 +38,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importBeatmapsButton = new SettingsButton
{
- Text = "Import beatmaps from stable",
+ Text = MaintenanceSettingsStrings.ImportBeatmapsFromStable,
Action = () =>
{
importBeatmapsButton.Enabled.Value = false;
@@ -48,7 +49,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteBeatmapsButton = new DangerousSettingsButton
{
- Text = "Delete ALL beatmaps",
+ Text = MaintenanceSettingsStrings.DeleteAllBeatmaps,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importScoresButton = new SettingsButton
{
- Text = "Import scores from stable",
+ Text = MaintenanceSettingsStrings.ImportScoresFromStable,
Action = () =>
{
importScoresButton.Enabled.Value = false;
@@ -74,7 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteScoresButton = new DangerousSettingsButton
{
- Text = "Delete ALL scores",
+ Text = MaintenanceSettingsStrings.DeleteAllScores,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -89,7 +90,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importSkinsButton = new SettingsButton
{
- Text = "Import skins from stable",
+ Text = MaintenanceSettingsStrings.ImportSkinsFromStable,
Action = () =>
{
importSkinsButton.Enabled.Value = false;
@@ -100,7 +101,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(deleteSkinsButton = new DangerousSettingsButton
{
- Text = "Delete ALL skins",
+ Text = MaintenanceSettingsStrings.DeleteAllSkins,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
@@ -117,7 +118,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
Add(importCollectionsButton = new SettingsButton
{
- Text = "Import collections from stable",
+ Text = MaintenanceSettingsStrings.ImportCollectionsFromStable,
Action = () =>
{
importCollectionsButton.Enabled.Value = false;
@@ -128,7 +129,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Add(new DangerousSettingsButton
{
- Text = "Delete ALL collections",
+ Text = MaintenanceSettingsStrings.DeleteAllCollections,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll));
@@ -140,7 +141,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
restoreButton = new SettingsButton
{
- Text = "Restore all hidden difficulties",
+ Text = MaintenanceSettingsStrings.RestoreAllHiddenDifficulties,
Action = () =>
{
restoreButton.Enabled.Value = false;
@@ -153,7 +154,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
},
undeleteButton = new SettingsButton
{
- Text = "Restore all recently deleted beatmaps",
+ Text = MaintenanceSettingsStrings.RestoreAllRecentlyDeletedBeatmaps,
Action = () =>
{
undeleteButton.Enabled.Value = false;
diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
index 73c88b8e71..fa0c06167b 100644
--- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs
@@ -3,6 +3,8 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Maintenance;
using osuTK;
@@ -10,7 +12,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
public class MaintenanceSection : SettingsSection
{
- public override string Header => "Maintenance";
+ public override LocalisableString Header => MaintenanceSettingsStrings.MaintenanceSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
index 3a2de2ee36..351a32c72e 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/AlertsAndPrivacySettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Online
{
public class AlertsAndPrivacySettings : SettingsSubsection
{
- protected override LocalisableString Header => "Alerts and Privacy";
+ protected override LocalisableString Header => OnlineSettingsStrings.AlertsAndPrivacyHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,12 +20,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsCheckbox
{
- LabelText = "Show a notification when someone mentions your name",
+ LabelText = OnlineSettingsStrings.NotifyOnMentioned,
Current = config.GetBindable(OsuSetting.NotifyOnUsernameMentioned)
},
new SettingsCheckbox
{
- LabelText = "Show a notification when you receive a private message",
+ LabelText = OnlineSettingsStrings.NotifyOnPrivateMessage,
Current = config.GetBindable(OsuSetting.NotifyOnPrivateMessage)
},
};
diff --git a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
index f2012f0d9c..0207f2fd01 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/IntegrationSettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Online
{
public class IntegrationSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Integrations";
+ protected override LocalisableString Header => OnlineSettingsStrings.IntegrationsHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsEnumDropdown
{
- LabelText = "Discord Rich Presence",
+ LabelText = OnlineSettingsStrings.DiscordRichPresence,
Current = config.GetBindable(OsuSetting.DiscordRichPresence)
}
};
diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
index 89e7b096f3..e864260cc6 100644
--- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs
@@ -5,12 +5,13 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.Online
{
public class WebSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Web";
+ protected override LocalisableString Header => OnlineSettingsStrings.WebHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -19,24 +20,24 @@ namespace osu.Game.Overlays.Settings.Sections.Online
{
new SettingsCheckbox
{
- LabelText = "Warn about opening external links",
+ LabelText = OnlineSettingsStrings.ExternalLinkWarning,
Current = config.GetBindable(OsuSetting.ExternalLinkWarning)
},
new SettingsCheckbox
{
- LabelText = "Prefer downloads without video",
+ LabelText = OnlineSettingsStrings.PreferNoVideo,
Keywords = new[] { "no-video" },
Current = config.GetBindable(OsuSetting.PreferNoVideo)
},
new SettingsCheckbox
{
- LabelText = "Automatically download beatmaps when spectating",
+ LabelText = OnlineSettingsStrings.AutomaticallyDownloadWhenSpectating,
Keywords = new[] { "spectator" },
Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating),
},
new SettingsCheckbox
{
- LabelText = "Show explicit content in search results",
+ LabelText = OnlineSettingsStrings.ShowExplicitContent,
Keywords = new[] { "nsfw", "18+", "offensive" },
Current = config.GetBindable(OsuSetting.ShowOnlineExplicitContent),
}
diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
index 680d11f7da..8b523b90b9 100644
--- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.Online;
namespace osu.Game.Overlays.Settings.Sections
{
public class OnlineSection : SettingsSection
{
- public override string Header => "Online";
+ public override LocalisableString Header => OnlineSettingsStrings.OnlineSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 9f3543d059..e0d8252930 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -13,6 +13,7 @@ using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
using osu.Game.Skinning;
using osu.Game.Skinning.Editor;
using osuTK;
@@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Settings.Sections
{
private SkinSettingsDropdown skinDropdown;
- public override string Header => "Skin";
+ public override LocalisableString Header => SkinSettingsStrings.SkinSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
@@ -69,34 +70,34 @@ namespace osu.Game.Overlays.Settings.Sections
skinDropdown = new SkinSettingsDropdown(),
new SettingsButton
{
- Text = "Skin layout editor",
+ Text = SkinSettingsStrings.SkinLayoutEditor,
Action = () => skinEditor?.Toggle(),
},
new ExportSkinButton(),
new SettingsSlider
{
- LabelText = "Gameplay cursor size",
+ LabelText = SkinSettingsStrings.GameplayCursorSize,
Current = config.GetBindable(OsuSetting.GameplayCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
- LabelText = "Adjust gameplay cursor size based on current beatmap",
+ LabelText = SkinSettingsStrings.AutoCursorSize,
Current = config.GetBindable(OsuSetting.AutoCursorSize)
},
new SettingsCheckbox
{
- LabelText = "Beatmap skins",
+ LabelText = SkinSettingsStrings.BeatmapSkins,
Current = config.GetBindable(OsuSetting.BeatmapSkins)
},
new SettingsCheckbox
{
- LabelText = "Beatmap colours",
+ LabelText = SkinSettingsStrings.BeatmapColours,
Current = config.GetBindable(OsuSetting.BeatmapColours)
},
new SettingsCheckbox
{
- LabelText = "Beatmap hitsounds",
+ LabelText = SkinSettingsStrings.BeatmapHitsounds,
Current = config.GetBindable(OsuSetting.BeatmapHitsounds)
},
};
@@ -200,7 +201,7 @@ namespace osu.Game.Overlays.Settings.Sections
[BackgroundDependencyLoader]
private void load()
{
- Text = "Export selected skin";
+ Text = SkinSettingsStrings.ExportSkinButton;
Action = export;
currentSkin = skins.CurrentSkin.GetBoundCopy();
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
index 4b26645ef3..0afbed5df5 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/GeneralSettings.cs
@@ -6,12 +6,13 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
public class GeneralSettings : SettingsSubsection
{
- protected override LocalisableString Header => "General";
+ protected override LocalisableString Header => UserInterfaceStrings.GeneralHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -20,23 +21,23 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Rotate cursor when dragging",
+ LabelText = UserInterfaceStrings.CursorRotation,
Current = config.GetBindable(OsuSetting.CursorRotation)
},
new SettingsSlider
{
- LabelText = "Menu cursor size",
+ LabelText = UserInterfaceStrings.MenuCursorSize,
Current = config.GetBindable(OsuSetting.MenuCursorSize),
KeyboardStep = 0.01f
},
new SettingsCheckbox
{
- LabelText = "Parallax",
+ LabelText = UserInterfaceStrings.Parallax,
Current = config.GetBindable(OsuSetting.MenuParallax)
},
new SettingsSlider
{
- LabelText = "Hold-to-confirm activation time",
+ LabelText = UserInterfaceStrings.HoldToConfirmActivationTime,
Current = config.GetBindable(OsuSetting.UIHoldActivationDelay),
KeyboardStep = 50
},
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
index 81bbcbb54a..40485a070c 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs
@@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
+using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Users;
@@ -13,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
public class MainMenuSettings : SettingsSubsection
{
- protected override LocalisableString Header => "Main Menu";
+ protected override LocalisableString Header => UserInterfaceStrings.MainMenuHeader;
private IBindable user;
@@ -28,27 +29,27 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Interface voices",
+ LabelText = UserInterfaceStrings.InterfaceVoices,
Current = config.GetBindable(OsuSetting.MenuVoice)
},
new SettingsCheckbox
{
- LabelText = "osu! music theme",
+ LabelText = UserInterfaceStrings.OsuMusicTheme,
Current = config.GetBindable(OsuSetting.MenuMusic)
},
new SettingsEnumDropdown
{
- LabelText = "Intro sequence",
+ LabelText = UserInterfaceStrings.IntroSequence,
Current = config.GetBindable(OsuSetting.IntroSequence),
},
backgroundSourceDropdown = new SettingsEnumDropdown
{
- LabelText = "Background source",
+ LabelText = UserInterfaceStrings.BackgroundSource,
Current = config.GetBindable(OsuSetting.MenuBackgroundSource),
},
new SettingsEnumDropdown
{
- LabelText = "Seasonal backgrounds",
+ LabelText = UserInterfaceStrings.SeasonalBackgrounds,
Current = config.GetBindable(OsuSetting.SeasonalBackgroundMode),
}
};
@@ -60,9 +61,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
user.BindValueChanged(u =>
{
- const string not_supporter_note = "Changes to this setting will only apply with an active osu!supporter tag.";
-
- backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? not_supporter_note : string.Empty;
+ backgroundSourceDropdown.WarningText = u.NewValue?.IsSupporter != true ? UserInterfaceStrings.NotSupporterNote : default;
}, true);
}
}
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
index 587155eb0d..6290046987 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterface/SongSelectSettings.cs
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
+using osu.Game.Localisation;
namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
@@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
private Bindable minStars;
private Bindable maxStars;
- protected override LocalisableString Header => "Song Select";
+ protected override LocalisableString Header => UserInterfaceStrings.SongSelectHeader;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
@@ -31,31 +32,31 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
{
new SettingsCheckbox
{
- LabelText = "Right mouse drag to absolute scroll",
+ LabelText = UserInterfaceStrings.RightMouseScroll,
Current = config.GetBindable(OsuSetting.SongSelectRightMouseScroll),
},
new SettingsCheckbox
{
- LabelText = "Show converted beatmaps",
+ LabelText = UserInterfaceStrings.ShowConvertedBeatmaps,
Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps),
},
new SettingsSlider
{
- LabelText = "Display beatmaps from",
+ LabelText = UserInterfaceStrings.StarsMinimum,
Current = config.GetBindable(OsuSetting.DisplayStarsMinimum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsSlider
{
- LabelText = "up to",
+ LabelText = UserInterfaceStrings.StarsMaximum,
Current = config.GetBindable(OsuSetting.DisplayStarsMaximum),
KeyboardStep = 0.1f,
Keywords = new[] { "minimum", "maximum", "star", "difficulty" }
},
new SettingsEnumDropdown
{
- LabelText = "Random selection algorithm",
+ LabelText = UserInterfaceStrings.RandomSelectionAlgorithm,
Current = config.GetBindable(OsuSetting.RandomSelectAlgorithm),
}
};
@@ -63,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface
private class MaximumStarsSlider : StarsSlider
{
- public override LocalisableString TooltipText => Current.IsDefault ? "no limit" : base.TooltipText;
+ public override LocalisableString TooltipText => Current.IsDefault ? UserInterfaceStrings.NoLimit : base.TooltipText;
}
private class StarsSlider : OsuSliderBar
diff --git a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
index 718fea5f2b..6228c4c99a 100644
--- a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs
@@ -3,13 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
+using osu.Framework.Localisation;
+using osu.Game.Localisation;
using osu.Game.Overlays.Settings.Sections.UserInterface;
namespace osu.Game.Overlays.Settings.Sections
{
public class UserInterfaceSection : SettingsSection
{
- public override string Header => "User Interface";
+ public override LocalisableString Header => UserInterfaceStrings.UserInterfaceSectionHeader;
public override Drawable CreateIcon() => new SpriteIcon
{
diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs
index bd17c02af9..ef2027fdab 100644
--- a/osu.Game/Overlays/Settings/SettingsItem.cs
+++ b/osu.Game/Overlays/Settings/SettingsItem.cs
@@ -73,13 +73,7 @@ namespace osu.Game.Overlays.Settings
return;
// construct lazily for cases where the label is not needed (may be provided by the Control).
- FlowContent.Add(warningText = new OsuTextFlowContainer
- {
- Colour = colours.Yellow,
- Margin = new MarginPadding { Bottom = 5 },
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- });
+ FlowContent.Add(warningText = new SettingsNoticeText(colours) { Margin = new MarginPadding { Bottom = 5 } });
}
warningText.Alpha = hasValue ? 0 : 1;
diff --git a/osu.Game/Overlays/Settings/SettingsNoticeText.cs b/osu.Game/Overlays/Settings/SettingsNoticeText.cs
new file mode 100644
index 0000000000..76ecf7edd4
--- /dev/null
+++ b/osu.Game/Overlays/Settings/SettingsNoticeText.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;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+
+namespace osu.Game.Overlays.Settings
+{
+ public class SettingsNoticeText : LinkFlowContainer
+ {
+ public SettingsNoticeText(OsuColour colours)
+ : base(s => s.Colour = colours.Yellow)
+ {
+ RelativeSizeAxes = Axes.X;
+ AutoSizeAxes = Axes.Y;
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs
index 4143605c28..f993a46dc6 100644
--- a/osu.Game/Overlays/Settings/SettingsSection.cs
+++ b/osu.Game/Overlays/Settings/SettingsSection.cs
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK.Graphics;
@@ -19,10 +20,10 @@ namespace osu.Game.Overlays.Settings
protected override Container Content => FlowContent;
public abstract Drawable CreateIcon();
- public abstract string Header { get; }
+ public abstract LocalisableString Header { get; }
public IEnumerable FilterableChildren => Children.OfType();
- public virtual IEnumerable FilterTerms => new[] { Header };
+ public virtual IEnumerable FilterTerms => new[] { Header.ToString() };
private const int header_size = 26;
private const int margin = 20;
diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs
index df32424b67..4aa9360452 100644
--- a/osu.Game/Overlays/Settings/SettingsSubsection.cs
+++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs
@@ -24,6 +24,11 @@ namespace osu.Game.Overlays.Settings
protected abstract LocalisableString Header { get; }
public IEnumerable FilterableChildren => Children.OfType();
+
+ // FilterTerms should contains both original string and localised string for user to search.
+ // Since LocalisableString is unable to get original string at this time (2021-08-14),
+ // only call .ToString() to use localised one.
+ // TODO: Update here when FilterTerms accept LocalisableString.
public virtual IEnumerable FilterTerms => new[] { Header.ToString() };
public bool MatchingFilter
diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs
index 54b780615d..55e8aee266 100644
--- a/osu.Game/Overlays/SettingsOverlay.cs
+++ b/osu.Game/Overlays/SettingsOverlay.cs
@@ -9,7 +9,6 @@ using osu.Game.Overlays.Settings.Sections;
using osu.Game.Overlays.Settings.Sections.Input;
using osuTK.Graphics;
using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Localisation;
@@ -38,6 +37,8 @@ namespace osu.Game.Overlays
private readonly List subPanels = new List();
+ private SettingsSubPanel lastOpenedSubPanel;
+
protected override Drawable CreateHeader() => new SettingsHeader(Title, Description);
protected override Drawable CreateFooter() => new SettingsFooter();
@@ -46,21 +47,21 @@ namespace osu.Game.Overlays
{
}
- public override bool AcceptsFocus => subPanels.All(s => s.State.Value != Visibility.Visible);
+ public override bool AcceptsFocus => lastOpenedSubPanel == null || lastOpenedSubPanel.State.Value == Visibility.Hidden;
private T createSubPanel(T subPanel)
where T : SettingsSubPanel
{
subPanel.Depth = 1;
subPanel.Anchor = Anchor.TopRight;
- subPanel.State.ValueChanged += subPanelStateChanged;
+ subPanel.State.ValueChanged += e => subPanelStateChanged(subPanel, e);
subPanels.Add(subPanel);
return subPanel;
}
- private void subPanelStateChanged(ValueChangedEvent state)
+ private void subPanelStateChanged(SettingsSubPanel panel, ValueChangedEvent state)
{
switch (state.NewValue)
{
@@ -68,7 +69,9 @@ namespace osu.Game.Overlays
Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint);
SectionsContainer.FadeOut(300, Easing.OutQuint);
- ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint);
+ ContentContainer.MoveToX(-PANEL_WIDTH, 500, Easing.OutQuint);
+
+ lastOpenedSubPanel = panel;
break;
case Visibility.Hidden:
@@ -80,7 +83,7 @@ namespace osu.Game.Overlays
}
}
- protected override float ExpandedPosition => subPanels.Any(s => s.State.Value == Visibility.Visible) ? -WIDTH : base.ExpandedPosition;
+ protected override float ExpandedPosition => lastOpenedSubPanel?.State.Value == Visibility.Visible ? -PANEL_WIDTH : base.ExpandedPosition;
[BackgroundDependencyLoader]
private void load()
diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs
index eae828c142..f1c41c4b50 100644
--- a/osu.Game/Overlays/SettingsPanel.cs
+++ b/osu.Game/Overlays/SettingsPanel.cs
@@ -28,7 +28,15 @@ namespace osu.Game.Overlays
private const float sidebar_width = Sidebar.DEFAULT_WIDTH;
- public const float WIDTH = 400;
+ ///
+ /// The width of the settings panel content, excluding the sidebar.
+ ///
+ public const float PANEL_WIDTH = 400;
+
+ ///
+ /// The full width of the settings panel, including the sidebar.
+ ///
+ public const float WIDTH = sidebar_width + PANEL_WIDTH;
protected Container ContentContainer;
@@ -64,7 +72,8 @@ namespace osu.Game.Overlays
{
InternalChild = ContentContainer = new NonMaskedContent
{
- Width = WIDTH,
+ X = -WIDTH + ExpandedPosition,
+ Width = PANEL_WIDTH,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
index 067657159b..186514e868 100644
--- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
+++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs
@@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Mods
{
// This is required as SettingsItem relies heavily on this bindable for internal use.
// The actual update flow is done via the bindable provided in the constructor.
- public Bindable Current { get; set; } = new Bindable();
+ private readonly BindableWithCurrent current = new BindableWithCurrent();
+
+ public Bindable Current
+ {
+ get => current.Current;
+ set => current.Current = value;
+ }
public SliderControl(BindableNumber currentNumber)
{
diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs
index c0d7bae2b2..4425ece513 100644
--- a/osu.Game/Rulesets/Mods/ModFailCondition.cs
+++ b/osu.Game/Rulesets/Mods/ModFailCondition.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using osu.Framework.Bindables;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
@@ -11,9 +13,12 @@ namespace osu.Game.Rulesets.Mods
{
public override Type[] IncompatibleMods => new[] { typeof(ModNoFail), typeof(ModRelax), typeof(ModAutoplay) };
+ [SettingSource("Restart on fail", "Automatically restarts when failed.")]
+ public BindableBool Restart { get; } = new BindableBool();
+
public virtual bool PerformFail() => true;
- public virtual bool RestartOnFail => true;
+ public virtual bool RestartOnFail => Restart.Value;
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
{
diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs
index 187a4d8e23..9016a24f8d 100644
--- a/osu.Game/Rulesets/Mods/ModPerfect.cs
+++ b/osu.Game/Rulesets/Mods/ModPerfect.cs
@@ -21,6 +21,11 @@ namespace osu.Game.Rulesets.Mods
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModSuddenDeath)).ToArray();
+ protected ModPerfect()
+ {
+ Restart.Value = Restart.Default = true;
+ }
+
protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result)
=> result.Type.AffectsAccuracy()
&& result.Type != result.Judgement.MaxResult;
diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
index 4a1f1196a9..62b3d33069 100644
--- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
+++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs
@@ -15,7 +15,6 @@ using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
-using osu.Game.Skinning;
namespace osu.Game.Screens.Edit.Compose
{
@@ -73,7 +72,7 @@ namespace osu.Game.Screens.Edit.Compose
{
Debug.Assert(ruleset != null);
- return new RulesetSkinProvidingContainer(ruleset, EditorBeatmap.PlayableBeatmap, beatmap.Value.Skin).WithChild(content);
+ return new EditorSkinProvidingContainer(EditorBeatmap).WithChild(content);
}
#region Input Handling
diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs
index 61a3b0f5cc..e8d919311b 100644
--- a/osu.Game/Screens/Edit/Editor.cs
+++ b/osu.Game/Screens/Edit/Editor.cs
@@ -153,7 +153,7 @@ namespace osu.Game.Screens.Edit
// todo: remove caching of this and consume via editorBeatmap?
dependencies.Cache(beatDivisor);
- AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.Skin));
+ AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.GetSkin()));
dependencies.CacheAs(editorBeatmap);
changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs(changeHandler);
diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs
index 7de98e5e85..3402bf653a 100644
--- a/osu.Game/Screens/Edit/EditorBeatmap.cs
+++ b/osu.Game/Screens/Edit/EditorBeatmap.cs
@@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit
private readonly Bindable hasTiming = new Bindable();
[CanBeNull]
- public readonly ISkin BeatmapSkin;
+ public readonly EditorBeatmapSkin BeatmapSkin;
[Resolved]
private BindableBeatDivisor beatDivisor { get; set; }
@@ -69,7 +69,8 @@ namespace osu.Game.Screens.Edit
public EditorBeatmap(IBeatmap playableBeatmap, ISkin beatmapSkin = null)
{
PlayableBeatmap = playableBeatmap;
- BeatmapSkin = beatmapSkin;
+ if (beatmapSkin is Skin skin)
+ BeatmapSkin = new EditorBeatmapSkin(skin);
beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap);
diff --git a/osu.Game/Screens/Edit/EditorBeatmapSkin.cs b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs
new file mode 100644
index 0000000000..429df85904
--- /dev/null
+++ b/osu.Game/Screens/Edit/EditorBeatmapSkin.cs
@@ -0,0 +1,59 @@
+// 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.Audio.Sample;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.OpenGL.Textures;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Skinning;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Edit
+{
+ ///
+ /// A beatmap skin which is being edited.
+ ///
+ public class EditorBeatmapSkin : ISkin
+ {
+ public event Action BeatmapSkinChanged;
+
+ ///
+ /// The combo colours of this skin.
+ /// If empty, the default combo colours will be used.
+ ///
+ public readonly BindableList ComboColours;
+
+ private readonly Skin skin;
+
+ public EditorBeatmapSkin(Skin skin)
+ {
+ this.skin = skin;
+
+ ComboColours = new BindableList();
+ if (skin.Configuration.ComboColours != null)
+ ComboColours.AddRange(skin.Configuration.ComboColours.Select(c => (Colour4)c));
+ ComboColours.BindCollectionChanged((_, __) => updateColours());
+ }
+
+ private void invokeSkinChanged() => BeatmapSkinChanged?.Invoke();
+
+ private void updateColours()
+ {
+ skin.Configuration.CustomComboColours = ComboColours.Select(c => (Color4)c).ToList();
+ invokeSkinChanged();
+ }
+
+ #region Delegated ISkin implementation
+
+ public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
+ public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
+ public ISample GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
+ public IBindable GetConfig(TLookup lookup) => skin.GetConfig(lookup);
+
+ #endregion
+ }
+}
diff --git a/osu.Game/Screens/Edit/EditorSkinProvidingContainer.cs b/osu.Game/Screens/Edit/EditorSkinProvidingContainer.cs
new file mode 100644
index 0000000000..27563b5a0f
--- /dev/null
+++ b/osu.Game/Screens/Edit/EditorSkinProvidingContainer.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Game.Skinning;
+
+#nullable enable
+
+namespace osu.Game.Screens.Edit
+{
+ ///
+ /// A that fires when users have made a change to the beatmap skin
+ /// of the map being edited.
+ ///
+ public class EditorSkinProvidingContainer : RulesetSkinProvidingContainer
+ {
+ private readonly EditorBeatmapSkin? beatmapSkin;
+
+ public EditorSkinProvidingContainer(EditorBeatmap editorBeatmap)
+ : base(editorBeatmap.PlayableBeatmap.BeatmapInfo.Ruleset.CreateInstance(), editorBeatmap, editorBeatmap.BeatmapSkin)
+ {
+ beatmapSkin = editorBeatmap.BeatmapSkin;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ if (beatmapSkin != null)
+ beatmapSkin.BeatmapSkinChanged += TriggerSourceChanged;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (beatmapSkin != null)
+ beatmapSkin.BeatmapSkinChanged -= TriggerSourceChanged;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
index 6f92db98ee..d26856365e 100644
--- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
+++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs
@@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
- protected override ISkin GetSkin() => throw new NotImplementedException();
+ protected internal override ISkin GetSkin() => throw new NotImplementedException();
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
}
diff --git a/osu.Game/Screens/Edit/Setup/ColoursSection.cs b/osu.Game/Screens/Edit/Setup/ColoursSection.cs
index d7e16645f2..05d9855a24 100644
--- a/osu.Game/Screens/Edit/Setup/ColoursSection.cs
+++ b/osu.Game/Screens/Edit/Setup/ColoursSection.cs
@@ -1,14 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
-using System.Collections.Generic;
-using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterfaceV2;
-using osu.Game.Skinning;
-using osuTK.Graphics;
namespace osu.Game.Screens.Edit.Setup
{
@@ -31,9 +27,8 @@ namespace osu.Game.Screens.Edit.Setup
}
};
- var colours = Beatmap.BeatmapSkin?.GetConfig>(GlobalSkinColours.ComboColours)?.Value;
- if (colours != null)
- comboColours.Colours.AddRange(colours.Select(c => (Colour4)c));
+ if (Beatmap.BeatmapSkin != null)
+ comboColours.Colours.BindTo(Beatmap.BeatmapSkin.ComboColours);
}
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
index d8dfac496d..e2ba0b03b0 100644
--- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs
@@ -10,12 +10,12 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
public class OnlinePlayBackgroundSprite : OnlinePlayComposite
{
- private readonly BeatmapSetCoverType beatmapSetCoverType;
+ protected readonly BeatmapSetCoverType BeatmapSetCoverType;
private UpdateableBeatmapBackgroundSprite sprite;
public OnlinePlayBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
{
- this.beatmapSetCoverType = beatmapSetCoverType;
+ BeatmapSetCoverType = beatmapSetCoverType;
}
[BackgroundDependencyLoader]
@@ -33,6 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
sprite.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value;
}
- protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
+ protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomStatusInfo.cs b/osu.Game/Screens/OnlinePlay/Components/RoomStatusInfo.cs
deleted file mode 100644
index bcc256bcff..0000000000
--- a/osu.Game/Screens/OnlinePlay/Components/RoomStatusInfo.cs
+++ /dev/null
@@ -1,117 +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.Framework.Bindables;
-using osu.Framework.Extensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Online.Rooms;
-using osu.Game.Online.Rooms.RoomStatuses;
-
-namespace osu.Game.Screens.OnlinePlay.Components
-{
- public class RoomStatusInfo : OnlinePlayComposite
- {
- public RoomStatusInfo()
- {
- AutoSizeAxes = Axes.Both;
- }
-
- [BackgroundDependencyLoader]
- private void load()
- {
- StatusPart statusPart;
- EndDatePart endDatePart;
-
- InternalChild = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- statusPart = new StatusPart
- {
- Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14)
- },
- endDatePart = new EndDatePart { Font = OsuFont.GetFont(size: 14) }
- }
- };
-
- statusPart.EndDate.BindTo(EndDate);
- statusPart.Status.BindTo(Status);
- statusPart.Availability.BindTo(Availability);
- endDatePart.EndDate.BindTo(EndDate);
- }
-
- private class EndDatePart : DrawableDate
- {
- public readonly IBindable EndDate = new Bindable();
-
- public EndDatePart()
- : base(DateTimeOffset.UtcNow)
- {
- EndDate.BindValueChanged(date =>
- {
- // If null, set a very large future date to prevent unnecessary schedules.
- Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
- }, true);
- }
-
- protected override string Format()
- {
- if (EndDate.Value == null)
- return string.Empty;
-
- var diffToNow = Date.Subtract(DateTimeOffset.Now);
-
- if (diffToNow.TotalSeconds < -5)
- return $"Closed {base.Format()}";
-
- if (diffToNow.TotalSeconds < 0)
- return "Closed";
-
- if (diffToNow.TotalSeconds < 5)
- return "Closing soon";
-
- return $"Closing {base.Format()}";
- }
- }
-
- private class StatusPart : EndDatePart
- {
- public readonly IBindable Status = new Bindable();
- public readonly IBindable Availability = new Bindable();
-
- [Resolved]
- private OsuColour colours { get; set; }
-
- public StatusPart()
- {
- EndDate.BindValueChanged(_ => Format());
- Status.BindValueChanged(_ => Format());
- Availability.BindValueChanged(_ => Format());
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- Text = Format();
- }
-
- protected override string Format()
- {
- if (!IsLoaded)
- return string.Empty;
-
- RoomStatus status = Date < DateTimeOffset.Now ? new RoomStatusEnded() : Status.Value ?? new RoomStatusOpen();
-
- this.FadeColour(status.GetAppropriateColour(colours), 100);
- return $"{Availability.Value.GetDescription()}, {status.Message}";
- }
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
index 8c1b10e3bd..a27b27b8ad 100644
--- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs
@@ -1,12 +1,14 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
using 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.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Screens.Ranking.Expanded;
@@ -85,6 +87,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
minDisplay.Current.Value = minDifficulty;
maxDisplay.Current.Value = maxDifficulty;
+ maxDisplay.Alpha = Precision.AlmostEquals(Math.Round(minDifficulty.Stars, 2), Math.Round(maxDifficulty.Stars, 2)) ? 0 : 1;
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
diff --git a/osu.Game/Screens/OnlinePlay/Header.cs b/osu.Game/Screens/OnlinePlay/Header.cs
index bf0a53cbb6..b0db9256f5 100644
--- a/osu.Game/Screens/OnlinePlay/Header.cs
+++ b/osu.Game/Screens/OnlinePlay/Header.cs
@@ -2,19 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using Humanizer;
+using JetBrains.Annotations;
using osu.Framework.Allocation;
-using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Graphics.UserInterface;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Screens.OnlinePlay
{
@@ -22,52 +18,30 @@ namespace osu.Game.Screens.OnlinePlay
{
public const float HEIGHT = 80;
+ private readonly ScreenStack stack;
+ private readonly MultiHeaderTitle title;
+
public Header(string mainTitle, ScreenStack stack)
{
+ this.stack = stack;
+
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
+ Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING };
- HeaderBreadcrumbControl breadcrumbs;
- MultiHeaderTitle title;
-
- Children = new Drawable[]
+ Child = title = new MultiHeaderTitle(mainTitle)
{
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4Extensions.FromHex(@"#1f1921"),
- },
- new Container
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
- Children = new Drawable[]
- {
- title = new MultiHeaderTitle(mainTitle)
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.BottomLeft,
- },
- breadcrumbs = new HeaderBreadcrumbControl(stack)
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft
- }
- },
- },
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
};
- breadcrumbs.Current.ValueChanged += screen =>
- {
- if (screen.NewValue is IOnlinePlaySubScreen onlineSubScreen)
- title.Screen = onlineSubScreen;
- };
-
- breadcrumbs.Current.TriggerChange();
+ // unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to.
+ stack.ScreenPushed += (_, __) => updateSubScreenTitle();
+ stack.ScreenExited += (_, __) => updateSubScreenTitle();
}
+ private void updateSubScreenTitle() => title.Screen = stack.CurrentScreen as IOnlinePlaySubScreen;
+
private class MultiHeaderTitle : CompositeDrawable
{
private const float spacing = 6;
@@ -75,9 +49,10 @@ namespace osu.Game.Screens.OnlinePlay
private readonly OsuSpriteText dot;
private readonly OsuSpriteText pageTitle;
+ [CanBeNull]
public IOnlinePlaySubScreen Screen
{
- set => pageTitle.Text = value.ShortTitle.Titleize();
+ set => pageTitle.Text = value?.ShortTitle.Titleize() ?? string.Empty;
}
public MultiHeaderTitle(string mainTitle)
@@ -125,35 +100,5 @@ namespace osu.Game.Screens.OnlinePlay
pageTitle.Colour = dot.Colour = colours.Yellow;
}
}
-
- private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
- {
- public HeaderBreadcrumbControl(ScreenStack stack)
- : base(stack)
- {
- RelativeSizeAxes = Axes.X;
- StripColour = Color4.Transparent;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- AccentColour = Color4Extensions.FromHex("#e35c99");
- }
-
- protected override TabItem CreateTabItem(IScreen value) => new HeaderBreadcrumbTabItem(value)
- {
- AccentColour = AccentColour
- };
-
- private class HeaderBreadcrumbTabItem : BreadcrumbTabItem
- {
- public HeaderBreadcrumbTabItem(IScreen value)
- : base(value)
- {
- Bar.Colour = Color4.Transparent;
- }
- }
- }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
index 7da964d84b..c8ecd65574 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs
@@ -11,6 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
@@ -20,7 +21,6 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
-using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -28,6 +28,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osu.Game.Online.Rooms;
+using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components;
using osuTK;
using osuTK.Graphics;
@@ -37,21 +38,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler
{
public const float SELECTION_BORDER_WIDTH = 4;
- private const float corner_radius = 5;
+ private const float corner_radius = 10;
private const float transition_duration = 60;
- private const float content_padding = 10;
- private const float height = 110;
- private const float side_strip_width = 5;
- private const float cover_width = 145;
+ private const float height = 100;
public event Action StateChanged;
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
- private readonly Box selectionBox;
+ private Drawable selectionBox;
[Resolved(canBeNull: true)]
- private OnlinePlayScreen parentScreen { get; set; }
+ private LoungeSubScreen loungeScreen { get; set; }
[Resolved]
private BeatmapManager beatmaps { get; set; }
@@ -74,14 +72,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
get => state;
set
{
- if (value == state) return;
+ if (value == state)
+ return;
state = value;
- if (state == SelectionState.Selected)
- selectionBox.FadeIn(transition_duration);
- else
- selectionBox.FadeOut(transition_duration);
+ if (selectionBox != null)
+ {
+ if (state == SelectionState.Selected)
+ selectionBox.FadeIn(transition_duration);
+ else
+ selectionBox.FadeOut(transition_duration);
+ }
StateChanged?.Invoke(State);
}
@@ -108,6 +110,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
+ private int numberOfAvatars = 7;
+
+ public int NumberOfAvatars
+ {
+ get => numberOfAvatars;
+ set
+ {
+ numberOfAvatars = value;
+
+ if (recentParticipantsList != null)
+ recentParticipantsList.NumberOfCircles = value;
+ }
+ }
+
+ private readonly Bindable roomCategory = new Bindable();
+
+ private RecentParticipantsList recentParticipantsList;
+ private RoomSpecialCategoryPill specialCategoryPill;
+
public bool FilteringActive { get; set; }
private PasswordProtectedIcon passwordIcon;
@@ -119,114 +140,193 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
Room = room;
RelativeSizeAxes = Axes.X;
- Height = height + SELECTION_BORDER_WIDTH * 2;
- CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
- Masking = true;
+ Height = height;
- // create selectionBox here so State can be set before being loaded
- selectionBox = new Box
+ Masking = true;
+ CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
+ EdgeEffect = new EdgeEffectParameters
{
- RelativeSizeAxes = Axes.Both,
- Alpha = 0f,
+ Type = EdgeEffectType.Shadow,
+ Colour = Color4.Black.Opacity(40),
+ Radius = 5,
};
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, AudioManager audio)
+ private void load(OverlayColourProvider colours, AudioManager audio)
{
- float stripWidth = side_strip_width * (Room.Category.Value == RoomCategory.Spotlight ? 2 : 1);
-
Children = new Drawable[]
{
- new StatusColouredContainer(transition_duration)
+ // This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
+ new Box
{
RelativeSizeAxes = Axes.Both,
- Child = selectionBox
+ Colour = colours.Background5,
+ },
+ new OnlinePlayBackgroundSprite
+ {
+ RelativeSizeAxes = Axes.Both
},
new Container
{
+ Name = @"Room content",
RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding(SELECTION_BORDER_WIDTH),
+ // This negative padding resolves 1px gaps between this background and the background above.
+ Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
- EdgeEffect = new EdgeEffectParameters
- {
- Type = EdgeEffectType.Shadow,
- Colour = Color4.Black.Opacity(40),
- Radius = 5,
- },
Children = new Drawable[]
{
- new Box
+ new GridContainer
{
RelativeSizeAxes = Axes.Both,
- Colour = Color4Extensions.FromHex(@"212121"),
- },
- new StatusColouredContainer(transition_duration)
- {
- RelativeSizeAxes = Axes.Y,
- Width = stripWidth,
- Child = new Box { RelativeSizeAxes = Axes.Both }
- },
- new Container
- {
- RelativeSizeAxes = Axes.Y,
- Width = cover_width,
- Masking = true,
- Margin = new MarginPadding { Left = stripWidth },
- Child = new OnlinePlayBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both }
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.Relative, 0.2f)
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colours.Background5,
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
+ },
+ }
+ }
},
new Container
{
+ Name = @"Left details",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
- Vertical = content_padding,
- Left = stripWidth + cover_width + content_padding,
- Right = content_padding,
+ Left = 20,
+ Vertical = 5
},
Children = new Drawable[]
{
new FillFlowContainer
{
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
+ AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(5f),
Children = new Drawable[]
{
- new RoomName { Font = OsuFont.GetFont(size: 18) },
- new ParticipantInfo(),
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(5),
+ Children = new Drawable[]
+ {
+ new RoomStatusPill
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft
+ },
+ specialCategoryPill = new RoomSpecialCategoryPill
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft
+ },
+ new EndDateInfo
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft
+ },
+ }
+ },
+ new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Top = 3 },
+ Direction = FillDirection.Vertical,
+ Children = new Drawable[]
+ {
+ new RoomNameText(),
+ new RoomHostText(),
+ }
+ }
},
},
new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 5),
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(5),
Children = new Drawable[]
{
- new RoomStatusInfo(),
- new BeatmapTitle { TextSize = 14 },
- },
- },
- new ModeTypeInfo
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- },
+ new PlaylistCountPill
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ },
+ new StarRatingRangeDisplay
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Scale = new Vector2(0.8f)
+ }
+ }
+ }
+ }
+ },
+ new FillFlowContainer
+ {
+ Name = "Right content",
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Padding = new MarginPadding
+ {
+ Right = 10,
+ Vertical = 5
},
+ Children = new Drawable[]
+ {
+ recentParticipantsList = new RecentParticipantsList
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ NumberOfCircles = NumberOfAvatars
+ }
+ }
},
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
},
},
},
+ new StatusColouredContainer(transition_duration)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Child = selectionBox = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = state == SelectionState.Selected ? 1 : 0,
+ Masking = true,
+ CornerRadius = corner_radius,
+ BorderThickness = SELECTION_BORDER_WIDTH,
+ BorderColour = Color4.White,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0,
+ AlwaysPresent = true
+ }
+ }
+ },
};
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
@@ -250,6 +350,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
else
Alpha = 0;
+ roomCategory.BindTo(Room.Category);
+ roomCategory.BindValueChanged(c =>
+ {
+ if (c.NewValue == RoomCategory.Spotlight)
+ specialCategoryPill.Show();
+ else
+ specialCategoryPill.Hide();
+ }, true);
+
hasPassword.BindTo(Room.HasPassword);
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
}
@@ -260,7 +369,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
{
- parentScreen?.OpenNewRoom(Room.DeepClone());
+ lounge?.Open(Room.DeepClone());
})
};
@@ -307,11 +416,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return base.OnClick(e);
}
- private class RoomName : OsuSpriteText
+ private class RoomNameText : OsuSpriteText
{
[Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))]
private Bindable name { get; set; }
+ public RoomNameText()
+ {
+ Font = OsuFont.GetFont(size: 28);
+ }
+
[BackgroundDependencyLoader]
private void load()
{
@@ -319,6 +433,41 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
}
}
+ private class RoomHostText : OnlinePlayComposite
+ {
+ private LinkFlowContainer hostText;
+
+ public RoomHostText()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 16))
+ {
+ AutoSizeAxes = Axes.Both
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Host.BindValueChanged(host =>
+ {
+ hostText.Clear();
+
+ if (host.NewValue != null)
+ {
+ hostText.AddText("hosted by ");
+ hostText.AddUserLink(host.NewValue);
+ }
+ }, true);
+ }
+ }
+
public class PasswordProtectedIcon : CompositeDrawable
{
[BackgroundDependencyLoader]
@@ -366,7 +515,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private OsuPasswordTextBox passwordTextbox;
[BackgroundDependencyLoader]
- private void load(OsuColour colours)
+ private void load()
{
Child = new FillFlowContainer
{
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
new file mode 100644
index 0000000000..3207d373db
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
@@ -0,0 +1,65 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge.Components
+{
+ public class EndDateInfo : OnlinePlayComposite
+ {
+ public EndDateInfo()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = new EndDatePart
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
+ EndDate = { BindTarget = EndDate }
+ };
+ }
+
+ private class EndDatePart : DrawableDate
+ {
+ public readonly IBindable EndDate = new Bindable();
+
+ public EndDatePart()
+ : base(DateTimeOffset.UtcNow)
+ {
+ EndDate.BindValueChanged(date =>
+ {
+ // If null, set a very large future date to prevent unnecessary schedules.
+ Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
+ }, true);
+ }
+
+ protected override string Format()
+ {
+ if (EndDate.Value == null)
+ return string.Empty;
+
+ var diffToNow = Date.Subtract(DateTimeOffset.Now);
+
+ if (diffToNow.TotalSeconds < -5)
+ return $"Closed {base.Format()}";
+
+ if (diffToNow.TotalSeconds < 0)
+ return "Closed";
+
+ if (diffToNow.TotalSeconds < 5)
+ return "Closing soon";
+
+ return $"Closing {base.Format()}";
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterControl.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterControl.cs
deleted file mode 100644
index 7fc1c670ca..0000000000
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterControl.cs
+++ /dev/null
@@ -1,135 +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.Framework.Graphics.Shapes;
-using osu.Framework.Threading;
-using osu.Game.Graphics;
-using osu.Game.Graphics.UserInterface;
-using osu.Game.Rulesets;
-using osuTK.Graphics;
-
-namespace osu.Game.Screens.OnlinePlay.Lounge.Components
-{
- public abstract class FilterControl : CompositeDrawable
- {
- protected const float VERTICAL_PADDING = 10;
- protected const float HORIZONTAL_PADDING = 80;
-
- [Resolved(CanBeNull = true)]
- private Bindable filter { get; set; }
-
- [Resolved]
- private IBindable ruleset { get; set; }
-
- private readonly Box tabStrip;
- private readonly SearchTextBox search;
- private readonly PageTabControl tabs;
-
- protected FilterControl()
- {
- InternalChildren = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- Alpha = 0.25f,
- },
- tabStrip = new Box
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- Height = 1,
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding
- {
- Top = VERTICAL_PADDING,
- Horizontal = HORIZONTAL_PADDING
- },
- Children = new Drawable[]
- {
- search = new FilterSearchTextBox
- {
- RelativeSizeAxes = Axes.X,
- },
- tabs = new PageTabControl
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.X,
- },
- }
- }
- };
-
- tabs.Current.Value = RoomStatusFilter.Open;
- tabs.Current.TriggerChange();
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- filter ??= new Bindable();
- tabStrip.Colour = colours.Yellow;
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- search.Current.BindValueChanged(_ => updateFilterDebounced());
- ruleset.BindValueChanged(_ => UpdateFilter());
- tabs.Current.BindValueChanged(_ => UpdateFilter(), true);
- }
-
- private ScheduledDelegate scheduledFilterUpdate;
-
- private void updateFilterDebounced()
- {
- scheduledFilterUpdate?.Cancel();
- scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
- }
-
- protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
-
- private void updateFilter()
- {
- scheduledFilterUpdate?.Cancel();
-
- var criteria = CreateCriteria();
- criteria.SearchString = search.Current.Value;
- criteria.Status = tabs.Current.Value;
- criteria.Ruleset = ruleset.Value;
-
- filter.Value = criteria;
- }
-
- protected virtual FilterCriteria CreateCriteria() => new FilterCriteria();
-
- public bool HoldFocus
- {
- get => search.HoldFocus;
- set => search.HoldFocus = value;
- }
-
- public void TakeFocus() => search.TakeFocus();
-
- private class FilterSearchTextBox : SearchTextBox
- {
- [BackgroundDependencyLoader]
- private void load()
- {
- BackgroundUnfocused = OsuColour.Gray(0.06f);
- BackgroundFocused = OsuColour.Gray(0.12f);
- }
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs
deleted file mode 100644
index bc4506b78e..0000000000
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/ParticipantInfo.cs
+++ /dev/null
@@ -1,88 +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 Humanizer;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Game.Graphics.Sprites;
-using osu.Game.Users.Drawables;
-using osuTK;
-
-namespace osu.Game.Screens.OnlinePlay.Lounge.Components
-{
- public class ParticipantInfo : OnlinePlayComposite
- {
- public ParticipantInfo()
- {
- RelativeSizeAxes = Axes.X;
- Height = 15f;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- OsuSpriteText summary;
- Container flagContainer;
- LinkFlowContainer hostText;
-
- InternalChildren = new Drawable[]
- {
- new FillFlowContainer
- {
- AutoSizeAxes = Axes.X,
- RelativeSizeAxes = Axes.Y,
- Direction = FillDirection.Horizontal,
- Spacing = new Vector2(5f, 0f),
- Children = new Drawable[]
- {
- flagContainer = new Container
- {
- Width = 22f,
- RelativeSizeAxes = Axes.Y,
- },
- hostText = new LinkFlowContainer
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- AutoSizeAxes = Axes.Both
- }
- },
- },
- new FillFlowContainer
- {
- Anchor = Anchor.CentreRight,
- Origin = Anchor.CentreRight,
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Horizontal,
- Colour = colours.Gray9,
- Children = new[]
- {
- summary = new OsuSpriteText
- {
- Text = "0 participants",
- }
- },
- },
- };
-
- Host.BindValueChanged(host =>
- {
- hostText.Clear();
- flagContainer.Clear();
-
- if (host.NewValue != null)
- {
- hostText.AddText("hosted by ");
- hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Torus, weight: FontWeight.Bold, italics: true));
-
- flagContainer.Child = new UpdateableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both };
- }
- }, true);
-
- ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true);
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PillContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PillContainer.cs
new file mode 100644
index 0000000000..109851a16b
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PillContainer.cs
@@ -0,0 +1,81 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge.Components
+{
+ ///
+ /// Displays contents in a "pill".
+ ///
+ public class PillContainer : Container
+ {
+ private const float padding = 8;
+
+ public readonly Drawable Background;
+
+ protected override Container Content => content;
+ private readonly Container content;
+
+ public PillContainer()
+ {
+ AutoSizeAxes = Axes.X;
+ Height = 16;
+
+ InternalChild = new CircularContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.X,
+ RelativeSizeAxes = Axes.Y,
+ Masking = true,
+ Children = new[]
+ {
+ Background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4.Black,
+ Alpha = 0.5f
+ },
+ new Container
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Padding = new MarginPadding { Horizontal = padding },
+ Child = new GridContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
+ },
+ Content = new[]
+ {
+ new[]
+ {
+ new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Padding = new MarginPadding { Bottom = 2 },
+ Child = content = new Container
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
new file mode 100644
index 0000000000..2fe3c7b668
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs
@@ -0,0 +1,54 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Specialized;
+using Humanizer;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge.Components
+{
+ ///
+ /// A pill that displays the playlist item count.
+ ///
+ public class PlaylistCountPill : OnlinePlayComposite
+ {
+ private OsuTextFlowContainer count;
+
+ public PlaylistCountPill()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = new PillContainer
+ {
+ Child = count = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
+ {
+ AutoSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Playlist.BindCollectionChanged(updateCount, true);
+ }
+
+ private void updateCount(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ count.Clear();
+ count.AddText(Playlist.Count.ToString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
+ count.AddText(" ");
+ count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None));
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistsFilterControl.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistsFilterControl.cs
deleted file mode 100644
index a463742097..0000000000
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistsFilterControl.cs
+++ /dev/null
@@ -1,59 +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.Framework.Graphics.UserInterface;
-using osu.Game.Graphics.UserInterface;
-
-namespace osu.Game.Screens.OnlinePlay.Lounge.Components
-{
- public class PlaylistsFilterControl : FilterControl
- {
- private readonly Dropdown dropdown;
-
- public PlaylistsFilterControl()
- {
- AddInternal(dropdown = new SlimEnumDropdown
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.None,
- Width = 160,
- X = -HORIZONTAL_PADDING,
- Y = -30
- });
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- dropdown.Current.BindValueChanged(_ => UpdateFilter());
- }
-
- protected override FilterCriteria CreateCriteria()
- {
- var criteria = base.CreateCriteria();
-
- switch (dropdown.Current.Value)
- {
- case PlaylistsCategory.Normal:
- criteria.Category = "normal";
- break;
-
- case PlaylistsCategory.Spotlight:
- criteria.Category = "spotlight";
- break;
- }
-
- return criteria;
- }
-
- private enum PlaylistsCategory
- {
- Any,
- Normal,
- Spotlight
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RankRangePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RankRangePill.cs
new file mode 100644
index 0000000000..42fe0bfecd
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RankRangePill.cs
@@ -0,0 +1,80 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Screens.OnlinePlay.Multiplayer;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge.Components
+{
+ public class RankRangePill : MultiplayerRoomComposite
+ {
+ private OsuTextFlowContainer rankFlow;
+
+ public RankRangePill()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = new PillContainer
+ {
+ Child = new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Spacing = new Vector2(4),
+ Children = new Drawable[]
+ {
+ new SpriteIcon
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Size = new Vector2(8),
+ Icon = FontAwesome.Solid.User
+ },
+ rankFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ AutoSizeAxes = Axes.Both,
+ }
+ }
+ }
+ };
+ }
+
+ protected override void OnRoomUpdated()
+ {
+ base.OnRoomUpdated();
+
+ rankFlow.Clear();
+
+ if (Room == null || Room.Users.All(u => u.User == null))
+ {
+ rankFlow.AddText("-");
+ return;
+ }
+
+ int minRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min();
+ int maxRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max();
+
+ rankFlow.AddText("#");
+ rankFlow.AddText(minRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
+
+ rankFlow.AddText(" - ");
+
+ rankFlow.AddText("#");
+ rankFlow.AddText(maxRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RecentParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RecentParticipantsList.cs
new file mode 100644
index 0000000000..bc658f45e4
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RecentParticipantsList.cs
@@ -0,0 +1,278 @@
+// 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.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Overlays;
+using osu.Game.Users;
+using osu.Game.Users.Drawables;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge.Components
+{
+ public class RecentParticipantsList : OnlinePlayComposite
+ {
+ private const float avatar_size = 36;
+
+ private FillFlowContainer avatarFlow;
+
+ private HiddenUserCount hiddenUsers;
+ private OsuSpriteText totalCount;
+
+ public RecentParticipantsList()
+ {
+ AutoSizeAxes = Axes.X;
+ Height = 60;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colours)
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ CornerRadius = 10,
+ Shear = new Vector2(0.2f, 0),
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colours.Background4,
+ }
+ },
+ new FillFlowContainer
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(4),
+ Padding = new MarginPadding { Right = 16 },
+ Children = new Drawable[]
+ {
+ new SpriteIcon
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Size = new Vector2(16),
+ Margin = new MarginPadding { Left = 8 },
+ Icon = FontAwesome.Solid.User,
+ },
+ totalCount = new OsuSpriteText
+ {
+ Font = OsuFont.Default.With(weight: FontWeight.Bold),
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ },
+ avatarFlow = new FillFlowContainer
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(4),
+ Margin = new MarginPadding { Left = 4 },
+ },
+ hiddenUsers = new HiddenUserCount
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ }
+ }
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
+ ParticipantCount.BindValueChanged(_ =>
+ {
+ updateHiddenUsers();
+ totalCount.Text = ParticipantCount.Value.ToString();
+ }, true);
+ }
+
+ private int numberOfCircles = 4;
+
+ ///
+ /// The maximum number of circles visible (including the "hidden count" circle in the overflow case).
+ ///
+ public int NumberOfCircles
+ {
+ get => numberOfCircles;
+ set
+ {
+ numberOfCircles = value;
+
+ if (LoadState < LoadState.Loaded)
+ return;
+
+ // Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible.
+ clearUsers();
+ foreach (var u in RecentParticipants)
+ addUser(u);
+
+ updateHiddenUsers();
+ }
+ }
+
+ private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ switch (e.Action)
+ {
+ case NotifyCollectionChangedAction.Add:
+ foreach (var added in e.NewItems.OfType())
+ addUser(added);
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ foreach (var removed in e.OldItems.OfType())
+ removeUser(removed);
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ clearUsers();
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ case NotifyCollectionChangedAction.Move:
+ // Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases.
+ clearUsers();
+ foreach (var u in RecentParticipants)
+ addUser(u);
+ break;
+ }
+
+ updateHiddenUsers();
+ }
+
+ private int displayedCircles => avatarFlow.Count + (hiddenUsers.Count > 0 ? 1 : 0);
+
+ private void addUser(User user)
+ {
+ if (displayedCircles < NumberOfCircles)
+ avatarFlow.Add(new CircularAvatar { User = user });
+ }
+
+ private void removeUser(User user)
+ {
+ avatarFlow.RemoveAll(a => a.User == user);
+ }
+
+ private void clearUsers()
+ {
+ avatarFlow.Clear();
+ updateHiddenUsers();
+ }
+
+ private void updateHiddenUsers()
+ {
+ int hiddenCount = 0;
+ if (RecentParticipants.Count > NumberOfCircles)
+ hiddenCount = ParticipantCount.Value - NumberOfCircles + 1;
+
+ hiddenUsers.Count = hiddenCount;
+
+ if (displayedCircles > NumberOfCircles)
+ avatarFlow.Remove(avatarFlow.Last());
+ else if (displayedCircles < NumberOfCircles)
+ {
+ var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
+ if (nextUser != null) addUser(nextUser);
+ }
+ }
+
+ private class CircularAvatar : CompositeDrawable
+ {
+ public User User
+ {
+ get => avatar.User;
+ set => avatar.User = value;
+ }
+
+ private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both };
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colours)
+ {
+ Size = new Vector2(avatar_size);
+
+ InternalChild = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ Colour = colours.Background5,
+ RelativeSizeAxes = Axes.Both,
+ },
+ avatar
+ }
+ };
+ }
+ }
+
+ public class HiddenUserCount : CompositeDrawable
+ {
+ public int Count
+ {
+ get => count;
+ set
+ {
+ count = value;
+ countText.Text = $"+{count}";
+
+ if (count > 0)
+ Show();
+ else
+ Hide();
+ }
+ }
+
+ private int count;
+
+ private readonly SpriteText countText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.Default.With(weight: FontWeight.Bold),
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colours)
+ {
+ Size = new Vector2(avatar_size);
+ Alpha = 0;
+
+ InternalChild = new CircularContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ Masking = true,
+ Children = new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colours.Background5,
+ },
+ countText
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs
deleted file mode 100644
index a0a7f2dc28..0000000000
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInfo.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
-// See the LICENCE file in the repository root for full licence text.
-
-using System.Collections.Generic;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Game.Graphics;
-using osu.Game.Graphics.Containers;
-using osu.Game.Screens.OnlinePlay.Components;
-using osuTK;
-
-namespace osu.Game.Screens.OnlinePlay.Lounge.Components
-{
- public class RoomInfo : OnlinePlayComposite
- {
- private readonly List statusElements = new List();
- private readonly OsuTextFlowContainer roomName;
-
- public RoomInfo()
- {
- AutoSizeAxes = Axes.Y;
-
- RoomLocalUserInfo localUserInfo;
- RoomStatusInfo statusInfo;
- ModeTypeInfo typeInfo;
- ParticipantInfo participantInfo;
-
- InternalChild = new FillFlowContainer
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.X,
- Spacing = new Vector2(0, 10),
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30))
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- },
- participantInfo = new ParticipantInfo(),
- new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Children = new Drawable[]
- {
- statusInfo = new RoomStatusInfo(),
- typeInfo = new ModeTypeInfo
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight
- }
- }
- },
- localUserInfo = new RoomLocalUserInfo(),
- }
- };
-
- statusElements.AddRange(new Drawable[]
- {
- statusInfo, typeInfo, participantInfo, localUserInfo
- });
- }
-
- protected override void LoadComplete()
- {
- base.LoadComplete();
- if (RoomID.Value == null)
- statusElements.ForEach(e => e.FadeOut());
- RoomID.BindValueChanged(id =>
- {
- if (id.NewValue == null)
- statusElements.ForEach(e => e.FadeOut(100));
- else
- statusElements.ForEach(e => e.FadeIn(100));
- }, true);
- RoomName.BindValueChanged(name =>
- {
- roomName.Text = name.NewValue ?? "No room selected";
- }, true);
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInspector.cs
deleted file mode 100644
index c28354c753..0000000000
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomInspector.cs
+++ /dev/null
@@ -1,91 +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.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Beatmaps;
-using osu.Game.Graphics;
-using osu.Game.Screens.OnlinePlay.Components;
-using osuTK.Graphics;
-
-namespace osu.Game.Screens.OnlinePlay.Lounge.Components
-{
- public class RoomInspector : OnlinePlayComposite
- {
- private const float transition_duration = 100;
-
- private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
-
- [Resolved]
- private BeatmapManager beatmaps { get; set; }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- OverlinedHeader participantsHeader;
-
- InternalChildren = new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = Color4.Black,
- Alpha = 0.25f
- },
- new Container
- {
- RelativeSizeAxes = Axes.Both,
- Padding = new MarginPadding { Horizontal = 30 },
- Child = new GridContainer
- {
- RelativeSizeAxes = Axes.Both,
- Content = new[]
- {
- new Drawable[]
- {
- new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- Children = new Drawable[]
- {
- new RoomInfo
- {
- RelativeSizeAxes = Axes.X,
- Margin = new MarginPadding { Vertical = 60 },
- },
- participantsHeader = new OverlinedHeader("Recent Participants"),
- new ParticipantsDisplay(Direction.Vertical)
- {
- RelativeSizeAxes = Axes.X,
- Height = ParticipantsList.TILE_SIZE * 3,
- Details = { BindTarget = participantsHeader.Details }
- }
- }
- }
- },
- new Drawable[] { new OverlinedPlaylistHeader(), },
- new Drawable[]
- {
- new DrawableRoomPlaylist(false, false)
- {
- RelativeSizeAxes = Axes.Both,
- Items = { BindTarget = Playlist }
- },
- },
- },
- RowDimensions = new[]
- {
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(GridSizeMode.AutoSize),
- new Dimension(),
- }
- }
- }
- };
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs
new file mode 100644
index 0000000000..6cdbeb2af4
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomSpecialCategoryPill.cs
@@ -0,0 +1,49 @@
+// 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.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge.Components
+{
+ public class RoomSpecialCategoryPill : OnlinePlayComposite
+ {
+ private SpriteText text;
+
+ public RoomSpecialCategoryPill()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ InternalChild = new PillContainer
+ {
+ Background =
+ {
+ Colour = colours.Pink,
+ Alpha = 1
+ },
+ Child = text = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
+ Colour = Color4.Black
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Category.BindValueChanged(c => text.Text = c.NewValue.ToString(), true);
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs
new file mode 100644
index 0000000000..1d43f2dc65
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs
@@ -0,0 +1,74 @@
+// 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.Sprites;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Online.Rooms;
+using osu.Game.Online.Rooms.RoomStatuses;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.OnlinePlay.Lounge.Components
+{
+ ///
+ /// A pill that displays the room's current status.
+ ///
+ public class RoomStatusPill : OnlinePlayComposite
+ {
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ private PillContainer pill;
+ private SpriteText statusText;
+
+ public RoomStatusPill()
+ {
+ AutoSizeAxes = Axes.Both;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = pill = new PillContainer
+ {
+ Child = statusText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
+ Colour = Color4.Black
+ }
+ };
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ EndDate.BindValueChanged(_ => updateDisplay());
+ Status.BindValueChanged(_ => updateDisplay(), true);
+
+ FinishTransforms(true);
+ }
+
+ private void updateDisplay()
+ {
+ RoomStatus status = getDisplayStatus();
+
+ pill.Background.Alpha = 1;
+ pill.Background.FadeColour(status.GetAppropriateColour(colours), 100);
+ statusText.Text = status.Message;
+ }
+
+ private RoomStatus getDisplayStatus()
+ {
+ if (EndDate.Value < DateTimeOffset.Now)
+ return new RoomStatusEnded();
+
+ return Status.Value;
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
index d2253b2d2c..46d9850fde 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs
@@ -50,6 +50,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
+ // account for the fact we are in a scroll container and want a bit of spacing from the scroll bar.
+ Padding = new MarginPadding { Right = 5 };
+
InternalChild = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.X,
@@ -59,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
- Spacing = new Vector2(2),
+ Spacing = new Vector2(10),
}
};
}
@@ -137,7 +140,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
roomFlow.Remove(toRemove);
- selectedRoom.Value = null;
+ // selection may have a lease due to being in a sub screen.
+ if (!selectedRoom.Disabled)
+ selectedRoom.Value = null;
}
}
@@ -149,7 +154,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
protected override bool OnClick(ClickEvent e)
{
- selectedRoom.Value = null;
+ if (!selectedRoom.Disabled)
+ selectedRoom.Value = null;
return base.OnClick(e);
}
@@ -211,6 +217,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private void selectNext(int direction)
{
+ if (selectedRoom.Disabled)
+ return;
+
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
Room room;
diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
index 68bd3cd613..e0e5cc415e 100644
--- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs
@@ -2,6 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System;
+using System.Diagnostics;
+using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
@@ -9,15 +11,20 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Screens;
+using osu.Framework.Threading;
+using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
+using osu.Game.Rulesets;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Users;
+using osuTK;
namespace osu.Game.Screens.OnlinePlay.Lounge
{
@@ -28,11 +35,16 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
+ protected Container Buttons { get; } = new Container
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ AutoSizeAxes = Axes.Both
+ };
+
private readonly IBindable initialRoomsReceived = new Bindable();
private readonly IBindable operationInProgress = new Bindable();
- private FilterControl filter;
- private Container content;
private LoadingLayer loadingLayer;
[Resolved]
@@ -44,53 +56,112 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[Resolved(CanBeNull = true)]
private OngoingOperationTracker ongoingOperationTracker { get; set; }
+ [Resolved(CanBeNull = true)]
+ private Bindable filter { get; set; }
+
+ [Resolved]
+ private IBindable ruleset { get; set; }
+
[CanBeNull]
private IDisposable joiningRoomOperation { get; set; }
private RoomsContainer roomsContainer;
+ private SearchTextBox searchTextBox;
+ private Dropdown statusDropdown;
+
+ [CanBeNull]
+ private LeasedBindable selectionLease;
[BackgroundDependencyLoader]
private void load()
{
+ filter ??= new Bindable(new FilterCriteria());
+
OsuScrollContainer scrollContainer;
- InternalChildren = new Drawable[]
+ InternalChildren = new[]
{
- content = new Container
+ loadingLayer = new LoadingLayer(true),
+ new Container
{
RelativeSizeAxes = Axes.Both,
- Children = new Drawable[]
+ Padding = new MarginPadding
{
- new Container
+ Left = WaveOverlayContainer.WIDTH_PADDING,
+ Right = WaveOverlayContainer.WIDTH_PADDING,
+ },
+ Child = new GridContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ RowDimensions = new[]
{
- RelativeSizeAxes = Axes.Both,
- Width = 0.55f,
- Children = new Drawable[]
+ new Dimension(GridSizeMode.Absolute, Header.HEIGHT),
+ new Dimension(GridSizeMode.Absolute, 25),
+ new Dimension(GridSizeMode.Absolute, 20)
+ },
+ Content = new[]
+ {
+ new Drawable[]
{
- scrollContainer = new OsuScrollContainer
+ searchTextBox = new LoungeSearchTextBox
+ {
+ Anchor = Anchor.CentreRight,
+ Origin = Anchor.CentreRight,
+ RelativeSizeAxes = Axes.X,
+ Width = 0.6f,
+ },
+ },
+ new Drawable[]
+ {
+ new Container
{
RelativeSizeAxes = Axes.Both,
- ScrollbarOverlapsContent = false,
- Padding = new MarginPadding(10),
- Child = roomsContainer = new RoomsContainer()
+ Depth = float.MinValue, // Contained filters should appear over the top of rooms.
+ Children = new Drawable[]
+ {
+ Buttons.WithChild(CreateNewRoomButton().With(d =>
+ {
+ d.Anchor = Anchor.BottomLeft;
+ d.Origin = Anchor.BottomLeft;
+ d.Size = new Vector2(150, 37.5f);
+ d.Action = () => Open();
+ })),
+ new FillFlowContainer
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Horizontal,
+ Spacing = new Vector2(10),
+ ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
+ {
+ d.Anchor = Anchor.TopRight;
+ d.Origin = Anchor.TopRight;
+ }))
+ }
+ }
+ }
+ },
+ null,
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ scrollContainer = new OsuScrollContainer
+ {
+ RelativeSizeAxes = Axes.Both,
+ ScrollbarOverlapsContent = false,
+ Child = roomsContainer = new RoomsContainer()
+ },
+ }
},
- loadingLayer = new LoadingLayer(true),
}
- },
- new RoomInspector
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Both,
- Width = 0.45f,
- },
+ }
},
},
- filter = CreateFilterControl().With(d =>
- {
- d.RelativeSizeAxes = Axes.X;
- d.Height = 80;
- })
};
// scroll selected room into view on selection.
@@ -106,6 +177,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
base.LoadComplete();
+ searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced());
+ ruleset.BindValueChanged(_ => UpdateFilter());
+
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
@@ -114,24 +188,49 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
}
+
+ updateFilter();
}
- protected override void UpdateAfterChildren()
- {
- base.UpdateAfterChildren();
+ #region Filtering
- content.Padding = new MarginPadding
+ protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
+
+ private ScheduledDelegate scheduledFilterUpdate;
+
+ private void updateFilterDebounced()
+ {
+ scheduledFilterUpdate?.Cancel();
+ scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
+ }
+
+ private void updateFilter()
+ {
+ scheduledFilterUpdate?.Cancel();
+ filter.Value = CreateFilterCriteria();
+ }
+
+ protected virtual FilterCriteria CreateFilterCriteria() => new FilterCriteria
+ {
+ SearchString = searchTextBox.Current.Value,
+ Ruleset = ruleset.Value,
+ Status = statusDropdown.Current.Value
+ };
+
+ protected virtual IEnumerable CreateFilterControls()
+ {
+ statusDropdown = new SlimEnumDropdown
{
- Top = filter.DrawHeight,
- Left = WaveOverlayContainer.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING,
- Right = WaveOverlayContainer.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING,
+ RelativeSizeAxes = Axes.None,
+ Width = 160,
};
+
+ statusDropdown.Current.BindValueChanged(_ => UpdateFilter());
+
+ yield return statusDropdown;
}
- protected override void OnFocus(FocusEvent e)
- {
- filter.TakeFocus();
- }
+ #endregion
public override void OnEntering(IScreen last)
{
@@ -144,6 +243,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
base.OnResuming(last);
+ Debug.Assert(selectionLease != null);
+
+ selectionLease.Return();
+ selectionLease = null;
+
if (selectedRoom.Value?.RoomID.Value == null)
selectedRoom.Value = new Room();
@@ -164,14 +268,19 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
base.OnSuspending(next);
}
+ protected override void OnFocus(FocusEvent e)
+ {
+ searchTextBox.TakeFocus();
+ }
+
private void onReturning()
{
- filter.HoldFocus = true;
+ searchTextBox.HoldFocus = true;
}
private void onLeaving()
{
- filter.HoldFocus = false;
+ searchTextBox.HoldFocus = false;
// ensure any password prompt is dismissed.
this.HidePopover();
@@ -199,23 +308,32 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
///
/// Push a room as a new subscreen.
///
- public void Open(Room room) => Schedule(() =>
+ /// An optional template to use when creating the room.
+ public void Open(Room room = null) => Schedule(() =>
{
// Handles the case where a room is clicked 3 times in quick succession
if (!this.IsCurrentScreen())
return;
- OpenNewRoom(room);
+ OpenNewRoom(room ?? CreateNewRoom());
});
protected virtual void OpenNewRoom(Room room)
{
- selectedRoom.Value = room;
+ selectionLease = selectedRoom.BeginLease(false);
+ Debug.Assert(selectionLease != null);
+ selectionLease.Value = room;
this.Push(CreateRoomSubScreen(room));
}
- protected abstract FilterControl CreateFilterControl();
+ protected abstract OsuButton CreateNewRoomButton();
+
+ ///
+ /// Creates a new room.
+ ///
+ /// The created .
+ protected abstract Room CreateNewRoom();
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
@@ -226,5 +344,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
else
loadingLayer.Hide();
}
+
+ private class LoungeSearchTextBox : SearchTextBox
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ BackgroundUnfocused = OsuColour.Gray(0.06f);
+ BackgroundFocused = OsuColour.Gray(0.12f);
+ }
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
index cd4dee5e3a..3801463095 100644
--- a/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/Components/CreateRoomButton.cs
@@ -12,6 +12,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
[BackgroundDependencyLoader]
private void load()
{
+ SpriteText.Font = SpriteText.Font.With(size: 14);
Triangles.TriangleScale = 1.5f;
}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index a53e253581..243d2abf74 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -8,8 +8,10 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
+using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
using osu.Framework.Screens;
using osu.Game.Audio;
using osu.Game.Beatmaps;
@@ -17,6 +19,7 @@ using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Screens.OnlinePlay.Match
{
@@ -61,8 +64,15 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected RoomSubScreen()
{
+ Padding = new MarginPadding { Top = Header.HEIGHT };
+
AddRangeInternal(new Drawable[]
{
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = Color4Extensions.FromHex(@"3e3a44") // This is super temporary.
+ },
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = SelectedItem }
@@ -250,5 +260,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
{
}
+
+ public class UserModSelectButton : PurpleTriangleButton
+ {
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayMatchScoreDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayMatchScoreDisplay.cs
new file mode 100644
index 0000000000..20a88545c5
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayMatchScoreDisplay.cs
@@ -0,0 +1,40 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer
+{
+ public class GameplayMatchScoreDisplay : MatchScoreDisplay
+ {
+ public Bindable Expanded = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ Scale = new Vector2(0.5f);
+
+ Expanded.BindValueChanged(expandedChanged, true);
+ }
+
+ private void expandedChanged(ValueChangedEvent expanded)
+ {
+ if (expanded.NewValue)
+ {
+ Score1Text.FadeIn(500, Easing.OutQuint);
+ Score2Text.FadeIn(500, Easing.OutQuint);
+ this.ResizeWidthTo(2, 500, Easing.OutQuint);
+ }
+ else
+ {
+ Score1Text.FadeOut(500, Easing.OutQuint);
+ Score2Text.FadeOut(500, Easing.OutQuint);
+ this.ResizeWidthTo(1, 500, Easing.OutQuint);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
index d906cc8110..45928505bb 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Multiplayer.cs
@@ -4,9 +4,7 @@
using osu.Framework.Allocation;
using osu.Framework.Logging;
using osu.Framework.Screens;
-using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
-using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
@@ -54,20 +52,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})");
}
- protected override Room CreateNewRoom() =>
- new Room
- {
- Name = { Value = $"{API.LocalUser}'s awesome room" },
- Category = { Value = RoomCategory.Realtime },
- Type = { Value = MatchType.HeadToHead },
- };
-
protected override string ScreenTitle => "Multiplayer";
protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager();
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
-
- protected override OsuButton CreateNewMultiplayerGameButton() => new CreateMultiplayerMatchButton();
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerFilterControl.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerFilterControl.cs
deleted file mode 100644
index 37e0fd109a..0000000000
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerFilterControl.cs
+++ /dev/null
@@ -1,17 +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.Game.Screens.OnlinePlay.Lounge.Components;
-
-namespace osu.Game.Screens.OnlinePlay.Multiplayer
-{
- public class MultiplayerFilterControl : FilterControl
- {
- protected override FilterCriteria CreateCriteria()
- {
- var criteria = base.CreateCriteria();
- criteria.Category = "realtime";
- return criteria;
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
index 7062994479..ad7882abc2 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs
@@ -3,6 +3,8 @@
using osu.Framework.Allocation;
using osu.Framework.Logging;
+using osu.Game.Graphics.UserInterface;
+using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Lounge;
@@ -13,13 +15,30 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class MultiplayerLoungeSubScreen : LoungeSubScreen
{
- protected override FilterControl CreateFilterControl() => new MultiplayerFilterControl();
-
- protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
+ [Resolved]
+ private IAPIProvider api { get; set; }
[Resolved]
private MultiplayerClient client { get; set; }
+ protected override FilterCriteria CreateFilterCriteria()
+ {
+ var criteria = base.CreateFilterCriteria();
+ criteria.Category = @"realtime";
+ return criteria;
+ }
+
+ protected override OsuButton CreateNewRoomButton() => new CreateMultiplayerMatchButton();
+
+ protected override Room CreateNewRoom() => new Room
+ {
+ Name = { Value = $"{api.LocalUser}'s awesome room" },
+ Category = { Value = RoomCategory.Realtime },
+ Type = { Value = MatchType.HeadToHead },
+ };
+
+ protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
+
protected override void OpenNewRoom(Room room)
{
if (client?.IsConnected.Value != true)
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 561fa220c8..1943ff668f 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- new PurpleTriangleButton
+ new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
@@ -274,7 +274,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
isConnected.BindValueChanged(connected =>
{
if (!connected.NewValue)
- Schedule(this.Exit);
+ handleRoomLost();
}, true);
currentRoom.BindValueChanged(room =>
@@ -284,7 +284,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
// the room has gone away.
// this could mean something happened during the join process, or an external connection issue occurred.
// one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97)
- Schedule(this.Exit);
+ handleRoomLost();
}
}, true);
}
@@ -448,9 +448,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private void onRoomUpdated()
{
+ // may happen if the client is kicked or otherwise removed from the room.
+ if (client.Room == null)
+ {
+ handleRoomLost();
+ return;
+ }
+
Scheduler.AddOnce(UpdateMods);
}
+ private void handleRoomLost() => Schedule(() =>
+ {
+ if (this.IsCurrentScreen())
+ this.Exit();
+ else
+ ValidForResume = false;
+ });
+
private void onLoadRequested()
{
if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable)
@@ -475,16 +490,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override Screen CreateGameplayScreen()
{
Debug.Assert(client.LocalUser != null);
+ Debug.Assert(client.Room != null);
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
+ MultiplayerRoomUser[] users = userIds.Select(id => client.Room.Users.First(u => u.UserID == id)).ToArray();
switch (client.LocalUser.State)
{
case MultiplayerUserState.Spectating:
- return new MultiSpectatorScreen(userIds);
+ return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
default:
- return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
+ return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, users));
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
index b54a4a7726..ca1a3710ab 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerPlayer.cs
@@ -3,9 +3,12 @@
using System;
using System.Diagnostics;
+using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
@@ -34,16 +37,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private MultiplayerGameplayLeaderboard leaderboard;
- private readonly int[] userIds;
+ private readonly MultiplayerRoomUser[] users;
private LoadingLayer loadingDisplay;
+ private FillFlowContainer leaderboardFlow;
///
/// Construct a multiplayer player.
///
/// The playlist item to be played.
- /// The users which are participating in this game.
- public MultiplayerPlayer(PlaylistItem playlistItem, int[] userIds)
+ /// The users which are participating in this game.
+ public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
: base(playlistItem, new PlayerConfiguration
{
AllowPause = false,
@@ -51,14 +55,41 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
AllowSkipping = false,
})
{
- this.userIds = userIds;
+ this.users = users;
}
[BackgroundDependencyLoader]
private void load()
{
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
+ HUDOverlay.Add(leaderboardFlow = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ });
+
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
- LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add);
+ LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l =>
+ {
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
+ ((IBindable)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
+
+ leaderboardFlow.Add(l);
+
+ if (leaderboard.TeamScores.Count >= 2)
+ {
+ LoadComponentAsync(new GameplayMatchScoreDisplay
+ {
+ Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
+ Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
+ Expanded = { BindTarget = HUDOverlay.ShowHud },
+ }, leaderboardFlow.Add);
+ }
+ });
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
}
@@ -67,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
base.LoadAsyncComplete();
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
if (!ValidForResume)
return; // token retrieval may have failed.
@@ -92,13 +126,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Debug.Assert(client.Room != null);
}
- protected override void LoadComplete()
- {
- base.LoadComplete();
-
- ((IBindable)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
- }
-
protected override void StartGameplay()
{
// block base call, but let the server know we are ready to start.
@@ -118,6 +145,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override void Update()
{
base.Update();
+
+ if (!LoadedBeatmapSuccessfully)
+ return;
+
adjustLeaderboardPosition();
}
@@ -125,7 +156,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
const float padding = 44; // enough margin to avoid the hit error display.
- leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
+ leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
}
private void onMatchStarted() => Scheduler.Add(() =>
@@ -150,7 +181,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override ResultsScreen CreateResults(ScoreInfo score)
{
Debug.Assert(RoomId.Value != null);
- return new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
+ return leaderboard.TeamScores.Count == 2
+ ? new MultiplayerTeamResultsScreen(score, RoomId.Value.Value, PlaylistItem, leaderboard.TeamScores)
+ : new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs
new file mode 100644
index 0000000000..14a779dedf
--- /dev/null
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerTeamResultsScreen.cs
@@ -0,0 +1,152 @@
+// 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.Framework.Graphics;
+using osu.Framework.Graphics.Colour;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Effects;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Localisation;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Sprites;
+using osu.Game.Localisation;
+using osu.Game.Online.Rooms;
+using osu.Game.Scoring;
+using osu.Game.Screens.Play.HUD;
+using osuTK;
+
+namespace osu.Game.Screens.OnlinePlay.Multiplayer
+{
+ public class MultiplayerTeamResultsScreen : MultiplayerResultsScreen
+ {
+ private readonly SortedDictionary teamScores;
+
+ private Container winnerBackground;
+ private Drawable winnerText;
+
+ public MultiplayerTeamResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, SortedDictionary teamScores)
+ : base(score, roomId, playlistItem)
+ {
+ if (teamScores.Count != 2)
+ throw new NotSupportedException(@"This screen currently only supports 2 teams");
+
+ this.teamScores = teamScores;
+ }
+
+ [Resolved]
+ private OsuColour colours { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ const float winner_background_half_height = 250;
+
+ VerticalScrollContent.Anchor = VerticalScrollContent.Origin = Anchor.TopCentre;
+ VerticalScrollContent.Scale = new Vector2(0.9f);
+ VerticalScrollContent.Y = 75;
+
+ var redScore = teamScores.First().Value;
+ var blueScore = teamScores.Last().Value;
+
+ LocalisableString winner;
+ Colour4 winnerColour;
+
+ int comparison = redScore.Value.CompareTo(blueScore.Value);
+
+ if (comparison < 0)
+ {
+ // team name should eventually be coming from the multiplayer match state.
+ winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Blue");
+ winnerColour = colours.TeamColourBlue;
+ }
+ else if (comparison > 0)
+ {
+ // team name should eventually be coming from the multiplayer match state.
+ winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Red");
+ winnerColour = colours.TeamColourRed;
+ }
+ else
+ {
+ winner = MultiplayerTeamResultsScreenStrings.TheTeamsAreTied;
+ winnerColour = Colour4.White.Opacity(0.5f);
+ }
+
+ AddRangeInternal(new Drawable[]
+ {
+ new MatchScoreDisplay
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Team1Score = { BindTarget = redScore },
+ Team2Score = { BindTarget = blueScore },
+ },
+ winnerBackground = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Alpha = 0,
+ Children = new[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = winner_background_half_height,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.BottomCentre,
+ Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0), Colour4.Black.Opacity(0.4f))
+ },
+ new Box
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = winner_background_half_height,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+ Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0.4f), Colour4.Black.Opacity(0))
+ }
+ }
+ },
+ (winnerText = new OsuSpriteText
+ {
+ Alpha = 0,
+ Font = OsuFont.Torus.With(size: 80, weight: FontWeight.Bold),
+ Text = winner,
+ Blending = BlendingParameters.Additive
+ }).WithEffect(new GlowEffect
+ {
+ Colour = winnerColour,
+ }).With(e =>
+ {
+ e.Anchor = Anchor.Centre;
+ e.Origin = Anchor.Centre;
+ })
+ });
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ using (BeginDelayedSequence(300))
+ {
+ const double fade_in_duration = 600;
+
+ winnerText.FadeInFromZero(fade_in_duration, Easing.InQuint);
+ winnerBackground.FadeInFromZero(fade_in_duration, Easing.InQuint);
+
+ winnerText
+ .ScaleTo(10)
+ .ScaleTo(1, 600, Easing.InQuad)
+ .Then()
+ .ScaleTo(1.02f, 1600, Easing.OutQuint)
+ .FadeOut(5000, Easing.InQuad);
+ winnerBackground.Delay(2200).FadeOut(2000);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
index 89431445d3..6f8c735b6e 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs
@@ -42,6 +42,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private ModDisplay userModsDisplay;
private StateDisplay userStateDisplay;
+ private IconButton kickButton;
+
public ParticipantPanel(MultiplayerRoomUser user)
{
User = user;
@@ -64,7 +66,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
new Dimension(GridSizeMode.Absolute, 18),
new Dimension(GridSizeMode.AutoSize),
- new Dimension()
+ new Dimension(),
+ new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
@@ -79,7 +82,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Colour = Color4Extensions.FromHex("#F7E65D"),
Alpha = 0
},
- new TeamDisplay(user),
+ new TeamDisplay(User),
new Container
{
RelativeSizeAxes = Axes.Both,
@@ -157,7 +160,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
Margin = new MarginPadding { Right = 10 },
}
}
- }
+ },
+ kickButton = new KickButton
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Alpha = 0,
+ Margin = new MarginPadding(4),
+ Action = () => Client.KickUser(User.UserID),
+ },
},
}
};
@@ -167,7 +178,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
base.OnRoomUpdated();
- if (Room == null)
+ if (Room == null || Client.LocalUser == null)
return;
const double fade_time = 50;
@@ -179,6 +190,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
+ if (Client.IsHost && !User.Equals(Client.LocalUser))
+ kickButton.FadeIn(fade_time);
+ else
+ kickButton.FadeOut(fade_time);
+
if (Room.Host?.Equals(User) == true)
crown.FadeIn(fade_time);
else
@@ -211,13 +227,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
{
// Ensure the local user is still host.
- if (Room.Host?.UserID != api.LocalUser.Value.Id)
+ if (!Client.IsHost)
return;
Client.TransferHost(targetUser);
+ }),
+ new OsuMenuItem("Kick", MenuItemType.Destructive, () =>
+ {
+ // Ensure the local user is still host.
+ if (!Client.IsHost)
+ return;
+
+ Client.KickUser(targetUser);
})
};
}
}
+
+ public class KickButton : IconButton
+ {
+ public KickButton()
+ {
+ Icon = FontAwesome.Solid.UserTimes;
+ TooltipText = "Kick";
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ IconHoverColour = colours.Red;
+ }
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
index 5a7073f9de..351b9b3673 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/TeamDisplay.cs
@@ -11,7 +11,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
-using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
@@ -19,16 +18,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
{
internal class TeamDisplay : MultiplayerRoomComposite
{
- private readonly User user;
+ private readonly MultiplayerRoomUser user;
+
private Drawable box;
[Resolved]
private OsuColour colours { get; set; }
- [Resolved]
- private MultiplayerClient client { get; set; }
-
- public TeamDisplay(User user)
+ public TeamDisplay(MultiplayerRoomUser user)
{
this.user = user;
@@ -61,7 +58,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
}
};
- if (user.Id == client.LocalUser?.UserID)
+ if (Client.LocalUser?.Equals(user) == true)
{
InternalChild = new OsuClickableContainer
{
@@ -79,9 +76,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
private void changeTeam()
{
- client.SendMatchRequest(new ChangeTeamRequest
+ Client.SendMatchRequest(new ChangeTeamRequest
{
- TeamID = ((client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
+ TeamID = ((Client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
});
}
@@ -93,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
// we don't have a way of knowing when an individual user's state has updated, so just handle on RoomUpdated for now.
- var userRoomState = Room?.Users.FirstOrDefault(u => u.UserID == user.Id)?.MatchState;
+ var userRoomState = Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState;
const double duration = 400;
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
index 55c4270c70..1614828a78 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorLeaderboard.cs
@@ -4,6 +4,7 @@
using System;
using JetBrains.Annotations;
using osu.Framework.Timing;
+using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD;
@@ -11,8 +12,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
{
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
{
- public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, int[] userIds)
- : base(scoreProcessor, userIds)
+ public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
+ : base(scoreProcessor, users)
{
}
@@ -32,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
((SpectatingTrackedUserData)data).Clock = null;
}
- protected override TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(userId, scoreProcessor);
+ protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor);
protected override void Update()
{
@@ -47,8 +48,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
[CanBeNull]
public IClock Clock;
- public SpectatingTrackedUserData(int userId, ScoreProcessor scoreProcessor)
- : base(userId, scoreProcessor)
+ public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
+ : base(user, scoreProcessor)
{
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
index 56ed7a9564..d10917259d 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Spectator;
using osu.Game.Screens.Play;
+using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Spectate;
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
@@ -45,20 +46,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
private PlayerArea currentAudioSource;
private bool canStartMasterClock;
+ private readonly MultiplayerRoomUser[] users;
+
///