1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-18 02:09:52 +08:00

Compare commits

...

357 Commits

282 changed files with 6912 additions and 2791 deletions
+3
View File
@@ -18,3 +18,6 @@ M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize(
M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead.
M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead.
M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead.
M:osuTK.MathHelper.Clamp(System.Int32,System.Int32,System.Int32)~System.Int32;Use Math.Clamp() instead.
M:osuTK.MathHelper.Clamp(System.Single,System.Single,System.Single)~System.Single;This osuTK helper has unsafe semantics when one of the bounds provided is NaN. Use Math.Clamp() instead.
M:osuTK.MathHelper.Clamp(System.Double,System.Double,System.Double)~System.Double;This osuTK helper has unsafe semantics when one of the bounds provided is NaN. Use Math.Clamp() instead.
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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 System.Threading;
@@ -9,7 +10,6 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Pippidon.Objects;
using osu.Game.Rulesets.Pippidon.UI;
using osuTK;
namespace osu.Game.Rulesets.Pippidon.Beatmaps
{
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Pippidon.Beatmaps
};
}
private int getLane(HitObject hitObject) => (int)MathHelper.Clamp(
private int getLane(HitObject hitObject) => (int)Math.Clamp(
(getUsablePosition(hitObject) - minPosition) / (maxPosition - minPosition) * PippidonPlayfield.LANE_COUNT, 0, PippidonPlayfield.LANE_COUNT - 1);
private float getUsablePosition(HitObject h) => (h as IHasYPosition)?.Y ?? ((IHasXPosition)h).X;
+1 -1
View File
@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.225.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.313.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
{
public abstract partial class CatchPlacementBlueprintTestScene : PlacementBlueprintTestScene
{
protected sealed override Ruleset CreateRuleset() => new CatchRuleset();
protected const double TIME_SNAP = 100;
protected DrawableCatchHitObject LastObject;
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
private float halfCatcherWidth;
public override int Version => 20220701;
public override int Version => 20250306;
public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
@@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
{
base.UpdateHitObjectFromPath(hitObject);
if (hitObject.Path.ControlPoints.Count <= 1 || !hitObject.Path.HasValidLength)
if (hitObject.Path.ControlPoints.Count <= 1 || !hitObject.Path.HasValidLengthForPlacement)
EditorBeatmap?.Remove(hitObject);
}
}
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -110,7 +111,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon
double duration = ObjectState.HitObject.StartTime - ObjectState.DisplayStartTime;
fadeContent.Alpha = MathHelper.Clamp(
fadeContent.Alpha = Math.Clamp(
Interpolation.ValueAt(
Time.Current, 1f, 0f,
ObjectState.DisplayStartTime + duration * lens_flare_start,
@@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
public abstract partial class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
{
protected sealed override Ruleset CreateRuleset() => new ManiaRuleset();
private readonly Column column;
[Cached(typeof(IReadOnlyList<Mod>))]
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
keyCount.Current.Value = 8;
});
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().SingleOrDefault()?.CurrentDialog, Is.InstanceOf<ReloadEditorDialog>);
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().SingleOrDefault()?.CurrentDialog, Is.InstanceOf<SaveAndReloadEditorDialog>);
AddStep("refuse", () => InputManager.Key(Key.Number2));
AddAssert("key count is 5", () => keyCount.Current.Value, () => Is.EqualTo(5));
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
{
keyCount.Current.Value = 8;
});
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().Single().CurrentDialog, Is.InstanceOf<ReloadEditorDialog>);
AddUntilStep("dialog visible", () => Game.ChildrenOfType<IDialogOverlay>().Single().CurrentDialog, Is.InstanceOf<SaveAndReloadEditorDialog>);
AddStep("acquiesce", () => InputManager.Key(Key.Number1));
AddUntilStep("beatmap became 8K", () => Game.Beatmap.Value.BeatmapInfo.Difficulty.CircleSize, () => Is.EqualTo(8));
}
@@ -3,10 +3,13 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Mania.Tests
{
@@ -14,6 +17,11 @@ namespace osu.Game.Rulesets.Mania.Tests
{
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
[SetUp]
public void SetUp() => Schedule(() => toggleTouchControls(false));
#region Without touch controls
[Test]
public void TestTouchInput()
{
@@ -63,6 +71,79 @@ namespace osu.Game.Rulesets.Mania.Tests
() => Does.Not.Contain(getColumn(0).Action.Value));
}
#endregion
#region With touch controls
[Test]
public void TestTouchAreaNotInitiallyVisible()
{
AddStep("enable touch controls", () => toggleTouchControls(true));
AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden);
}
[Test]
public void TestPressReceptors()
{
AddStep("enable touch controls", () => toggleTouchControls(true));
AddAssert("touch area not visible", () => getTouchOverlay()?.State.Value == Visibility.Hidden);
for (int i = 0; i < 4; i++)
{
int index = i;
AddStep($"touch receptor {index}", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre)));
AddAssert("action sent",
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
() => Does.Contain(getReceptor(index).Action.Value));
AddStep($"release receptor {index}", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getReceptor(index).ScreenSpaceDrawQuad.Centre)));
AddAssert("touch area visible", () => getTouchOverlay()?.State.Value == Visibility.Visible);
}
}
[Test]
public void TestColumnsNotTouchableWithTouchControls()
{
AddStep("enable touch controls", () => toggleTouchControls(true));
AddStep("touch receptor 0", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getReceptor(0).ScreenSpaceDrawQuad.Centre)));
AddAssert("action sent",
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
() => Does.Contain(getReceptor(0).Action.Value));
AddStep("release receptor 0", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getReceptor(0).ScreenSpaceDrawQuad.Centre)));
AddAssert("touch area visible", () => getTouchOverlay()?.State.Value == Visibility.Visible);
AddStep("touch column 0", () => InputManager.BeginTouch(new Touch(TouchSource.Touch1, getColumn(0).ScreenSpaceDrawQuad.Centre + new Vector2(0f, -50f))));
AddAssert("action not sent",
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
() => Does.Not.Contain(getColumn(0).Action.Value));
AddStep("release column 0", () => InputManager.EndTouch(new Touch(TouchSource.Touch1, getColumn(0).ScreenSpaceDrawQuad.Centre + new Vector2(0f, -50f))));
AddAssert("action not sent",
() => this.ChildrenOfType<ManiaInputManager>().SelectMany(m => m.KeyBindingContainer.PressedActions),
() => Does.Not.Contain(getColumn(0).Action.Value));
}
#endregion
private void toggleTouchControls(bool enabled)
{
var maniaConfig = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(CreatePlayerRuleset())!;
maniaConfig.SetValue(ManiaRulesetSetting.MobileLayout, enabled ? ManiaMobileLayout.LandscapeWithOverlay : ManiaMobileLayout.Portrait);
}
private ManiaTouchInputArea? getTouchOverlay() => this.ChildrenOfType<ManiaTouchInputArea>().SingleOrDefault();
private ManiaTouchInputArea.ColumnInputReceptor getReceptor(int index) => this.ChildrenOfType<ManiaTouchInputArea.ColumnInputReceptor>().ElementAt(index);
private Column getColumn(int index) => this.ChildrenOfType<Column>().ElementAt(index);
}
}
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
SetDefault(ManiaRulesetSetting.ScrollSpeed, 8.0, 1.0, 40.0, 0.1);
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
SetDefault(ManiaRulesetSetting.MobileLayout, ManiaMobileLayout.Portrait);
#pragma warning disable CS0618
// Although obsolete, this is still required to populate the bindable from the database in case migration is required.
@@ -55,6 +56,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
ScrollTime,
ScrollSpeed,
ScrollDirection,
TimingBasedNoteColouring
TimingBasedNoteColouring,
MobileLayout,
}
}
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
updatingKeyCount = true;
editor.Reload().ContinueWith(t =>
editor.SaveAndReload().ContinueWith(t =>
{
if (!t.GetResultSafely())
{
@@ -0,0 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Localisation;
using osu.Game.Localisation;
namespace osu.Game.Rulesets.Mania
{
public enum ManiaMobileLayout
{
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.PortraitExpandedColumns))]
Portrait,
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.LandscapeExpandedColumns))]
Landscape,
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.LandscapeTouchOverlay))]
LandscapeWithOverlay,
}
}
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Graphics;
using osu.Framework.Localisation;
@@ -44,8 +45,17 @@ namespace osu.Game.Rulesets.Mania
Keywords = new[] { "color" },
LabelText = RulesetSettingsStrings.TimingBasedColouring,
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
}
},
};
if (RuntimeInfo.IsMobile)
{
Add(new SettingsEnumDropdown<ManiaMobileLayout>
{
LabelText = RulesetSettingsStrings.MobileLayout,
Current = config.GetBindable<ManiaMobileLayout>(ManiaRulesetSetting.MobileLayout),
});
}
}
private partial class ManiaScrollSlider : RoundedSliderBar<double>
@@ -42,8 +42,7 @@ namespace osu.Game.Rulesets.Mania.Mods
var locations = column.OfType<Note>().Select(n => (startTime: n.StartTime, samples: n.Samples))
.Concat(column.OfType<HoldNote>().SelectMany(h => new[]
{
(startTime: h.StartTime, samples: h.GetNodeSamples(0)),
(startTime: h.EndTime, samples: h.GetNodeSamples(1))
(startTime: h.StartTime, samples: h.GetNodeSamples(0))
}))
.OrderBy(h => h.startTime).ToList();
@@ -26,9 +26,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
: base(barLine)
{
RelativeSizeAxes = Axes.X;
Height = 1;
}
[BackgroundDependencyLoader]
[BackgroundDependencyLoader(true)]
private void load()
{
AddInternal(new SkinnableDrawable(new ManiaSkinComponentLookup(ManiaSkinComponents.BarLine), _ => new DefaultBarLine())
@@ -36,8 +37,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
Major.BindValueChanged(major => Height = major.NewValue ? 1.7f : 1.2f, true);
}
protected override void OnApply()
@@ -53,7 +53,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
}
}
private void onDirectionChanged() => Y = direction.Value == ScrollingDirection.Up ? -judgement_y_position : judgement_y_position;
private void onDirectionChanged()
{
Anchor = direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
Y = direction.Value == ScrollingDirection.Up ? -judgement_y_position : judgement_y_position;
}
protected override SpriteText CreateJudgementText() =>
new OsuSpriteText
@@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
[BackgroundDependencyLoader]
private void load(DrawableHitObject drawableHitObject)
{
RelativeSizeAxes = Axes.Both;
RelativeSizeAxes = Axes.X;
// Avoid flickering due to no anti-aliasing of boxes by default.
var edgeSmoothness = new Vector2(0.3f);
@@ -75,6 +75,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Default
private void updateMajor(ValueChangedEvent<bool> major)
{
Height = major.NewValue ? 1.7f : 1.2f;
mainLine.Alpha = major.NewValue ? 0.5f : 0.2f;
leftAnchor.Alpha = rightAnchor.Alpha = major.NewValue ? mainLine.Alpha * 0.3f : 0;
}
@@ -0,0 +1,38 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Skinning;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
public partial class LegacyBarLine : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
float skinHeight = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.BarLineHeight)?.Value ?? 1;
RelativeSizeAxes = Axes.X;
Height = 1.2f * skinHeight;
Colour = skin.GetManiaSkinConfig<Color4>(LegacyManiaSkinConfigurationLookups.BarLineColour)?.Value ?? Color4.White;
// Avoid flickering due to no anti-aliasing of boxes by default.
var edgeSmoothness = new Vector2(0.3f);
AddInternal(new Box
{
Name = "Bar line",
EdgeSmoothness = edgeSmoothness,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
});
}
}
}
@@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
this.result = result;
this.animation = animation;
Anchor = Anchor.BottomCentre;
Origin = Anchor.Centre;
AutoSizeAxes = Axes.Both;
@@ -53,10 +52,18 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
float hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition)?.Value ?? 0;
float scorePosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.ScorePosition)?.Value ?? 0;
float absoluteHitPosition = 480f * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR - hitPosition;
float finalPosition = scorePosition - absoluteHitPosition;
float hitPositionFromTop = 480f * LegacyManiaSkinConfiguration.POSITION_SCALE_FACTOR - hitPosition;
Y = direction.Value == ScrollingDirection.Up ? -finalPosition : finalPosition;
if (scorePosition > hitPositionFromTop / 2f)
{
Anchor = direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
Y = direction.Value == ScrollingDirection.Up ? hitPositionFromTop - scorePosition : scorePosition - hitPositionFromTop;
}
else
{
Anchor = direction.Value == ScrollingDirection.Up ? Anchor.BottomCentre : Anchor.TopCentre;
Y = direction.Value == ScrollingDirection.Up ? -scorePosition : scorePosition;
}
}
public void PlayAnimation()
@@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
return new LegacyStageForeground();
case ManiaSkinComponents.BarLine:
return null; // Not yet implemented.
return new LegacyBarLine();
default:
throw new UnsupportedSkinComponentException(lookup);
+11 -1
View File
@@ -12,6 +12,7 @@ using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osu.Game.Extensions;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Skinning;
@@ -57,6 +58,8 @@ namespace osu.Game.Rulesets.Mania.UI
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>(Color4.Black);
private IBindable<ManiaMobileLayout> mobilePlayStyle = null!;
public Column(int index, bool isSpecial)
{
Index = index;
@@ -77,7 +80,7 @@ namespace osu.Game.Rulesets.Mania.UI
private ISkinSource skin { get; set; } = null!;
[BackgroundDependencyLoader]
private void load(GameHost host)
private void load(GameHost host, ManiaRulesetConfigManager? rulesetConfig)
{
SkinnableDrawable keyArea;
@@ -115,6 +118,9 @@ namespace osu.Game.Rulesets.Mania.UI
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
RegisterPool<HoldNoteBody, DrawableHoldNoteBody>(10, 50);
if (rulesetConfig != null)
mobilePlayStyle = rulesetConfig.GetBindable<ManiaMobileLayout>(ManiaRulesetSetting.MobileLayout);
}
private void onSourceChanged()
@@ -193,6 +199,10 @@ namespace osu.Game.Rulesets.Mania.UI
protected override bool OnTouchDown(TouchDownEvent e)
{
// if touch overlay is visible, disallow columns from handling touch directly.
if (mobilePlayStyle.Value == ManiaMobileLayout.LandscapeWithOverlay)
return false;
maniaInputManager?.KeyBindingContainer.TriggerPressed(Action.Value);
touchActivationCount++;
return true;
+75 -30
View File
@@ -1,14 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Layout;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Skinning;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
@@ -34,6 +39,8 @@ namespace osu.Game.Rulesets.Mania.UI
set => base.Masking = value;
}
private readonly LayoutValue layout = new LayoutValue(Invalidation.DrawSize);
public ColumnFlow(StageDefinition stageDefinition)
{
this.stageDefinition = stageDefinition;
@@ -52,42 +59,32 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < stageDefinition.Columns; i++)
columns.Add(new Container<TContent> { RelativeSizeAxes = Axes.Y });
AddLayout(layout);
}
private ISkinSource currentSkin;
[Resolved]
private ISkinSource skin { get; set; } = null!;
private readonly Bindable<ManiaMobileLayout> mobileLayout = new Bindable<ManiaMobileLayout>();
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
private void load(ManiaRulesetConfigManager? rulesetConfig)
{
currentSkin = skin;
rulesetConfig?.BindWith(ManiaRulesetSetting.MobileLayout, mobileLayout);
skin.SourceChanged += onSkinChanged;
onSkinChanged();
mobileLayout.BindValueChanged(_ => invalidateLayout());
skin.SourceChanged += invalidateLayout;
}
private void onSkinChanged()
protected override void Update()
{
for (int i = 0; i < stageDefinition.Columns; i++)
base.Update();
if (!layout.IsValid)
{
if (i > 0)
{
float spacing = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1))
?.Value ?? Stage.COLUMN_SPACING;
columns[i].Margin = new MarginPadding { Left = spacing };
}
float? width = currentSkin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i))
?.Value;
bool isSpecialColumn = stageDefinition.IsSpecialColumn(i);
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
width ??= isSpecialColumn ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
columns[i].Width = width.Value;
updateColumnSize();
layout.Validate();
}
}
@@ -101,12 +98,60 @@ namespace osu.Game.Rulesets.Mania.UI
Content[column] = columns[column].Child = content;
}
private void invalidateLayout() => layout.Invalidate();
private void updateColumnSize()
{
float mobileAdjust = 1f;
if (RuntimeInfo.IsMobile && mobileLayout.Value == ManiaMobileLayout.Landscape)
{
// GridContainer+CellContainer containing this stage (gets split up for dual stages).
Vector2? containingCell = this.FindClosestParent<Stage>()?.Parent?.DrawSize;
// Will be null in tests.
if (containingCell != null && containingCell.Value.X >= containingCell.Value.Y)
{
float aspectRatio = containingCell.Value.X / containingCell.Value.Y;
// 2.83 is a mostly arbitrary scale-up (170 / 60, based on original implementation for argon)
mobileAdjust = 2.83f * Math.Min(1, 7f / stageDefinition.Columns);
// 1.92 is a "reference" mobile screen aspect ratio for phones.
// We should scale it back for cases like tablets which aren't so extreme.
mobileAdjust *= aspectRatio / 1.92f;
}
}
for (int i = 0; i < stageDefinition.Columns; i++)
{
if (i > 0)
{
float spacing = skin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnSpacing, i - 1))
?.Value ?? Stage.COLUMN_SPACING;
columns[i].Margin = new MarginPadding { Left = spacing };
}
float? width = skin.GetConfig<ManiaSkinConfigurationLookup, float>(
new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.ColumnWidth, i))
?.Value;
bool isSpecialColumn = stageDefinition.IsSpecialColumn(i);
// only used by default skin (legacy skins get defaults set in LegacyManiaSkinConfiguration)
width ??= isSpecialColumn ? Column.SPECIAL_COLUMN_WIDTH : Column.COLUMN_WIDTH;
columns[i].Width = width.Value * mobileAdjust;
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (currentSkin != null)
currentSkin.SourceChanged -= onSkinChanged;
if (skin.IsNotNull())
skin.SourceChanged -= invalidateLayout;
}
}
}
@@ -29,7 +29,11 @@ namespace osu.Game.Rulesets.Mania.UI
direction.BindValueChanged(_ => onDirectionChanged(), true);
}
private void onDirectionChanged() => Y = direction.Value == ScrollingDirection.Up ? -judgement_y_position : judgement_y_position;
private void onDirectionChanged()
{
Anchor = direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
Y = direction.Value == ScrollingDirection.Up ? -judgement_y_position : judgement_y_position;
}
protected override void LoadComplete()
{
@@ -3,30 +3,23 @@
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
public partial class DrawableManiaJudgement : DrawableJudgement
{
private IBindable<ScrollingDirection> direction;
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
public DrawableManiaJudgement()
{
direction = scrollingInfo.Direction.GetBoundCopy();
direction.BindValueChanged(_ => onDirectionChanged(), true);
}
private void onDirectionChanged()
{
Anchor = direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
Origin = Anchor.Centre;
// Extend the dimensions of this drawable to the entire parenting container.
// This allows skin implementations (i.e. LegacyManiaJudgementPiece) to freely choose the anchor based on skin settings.
Anchor = Anchor.TopLeft;
Origin = Anchor.TopLeft;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(1f);
}
protected override Drawable CreateDefaultJudgement(HitResult result) => new DefaultManiaJudgementPiece(result);
@@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.UI
public IEnumerable<BarLine> BarLines;
public override bool RequiresPortraitOrientation => Beatmap.Stages.Count == 1;
public override bool RequiresPortraitOrientation => Beatmap.Stages.Count == 1 && mobileLayout.Value == ManiaMobileLayout.Portrait;
protected override bool RelativeScaleBeatLengths => true;
@@ -58,6 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
private readonly BindableDouble configScrollSpeed = new BindableDouble();
private readonly Bindable<ManiaMobileLayout> mobileLayout = new Bindable<ManiaMobileLayout>();
private double currentTimeRange;
protected double TargetTimeRange;
@@ -111,6 +112,28 @@ namespace osu.Game.Rulesets.Mania.UI
configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue));
TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value);
Config.BindWith(ManiaRulesetSetting.MobileLayout, mobileLayout);
mobileLayout.BindValueChanged(_ => updateMobileLayout(), true);
}
private ManiaTouchInputArea? touchInputArea;
private void updateMobileLayout()
{
switch (mobileLayout.Value)
{
case ManiaMobileLayout.LandscapeWithOverlay:
KeyBindingInputManager.Add(touchInputArea = new ManiaTouchInputArea(this));
break;
default:
if (touchInputArea != null)
KeyBindingInputManager.Remove(touchInputArea, true);
touchInputArea = null;
break;
}
}
protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount;
@@ -0,0 +1,203 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
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.Input.Events;
using osu.Game.Configuration;
using osuTK;
namespace osu.Game.Rulesets.Mania.UI
{
/// <summary>
/// An overlay that captures and displays osu!mania mouse and touch input.
/// </summary>
public partial class ManiaTouchInputArea : VisibilityContainer
{
private readonly DrawableManiaRuleset drawableRuleset;
// visibility state affects our child. we always want to handle input.
public override bool PropagatePositionalInputSubTree => true;
public override bool PropagateNonPositionalInputSubTree => true;
[SettingSource("Spacing", "The spacing between receptors.")]
public BindableFloat Spacing { get; } = new BindableFloat(10)
{
Precision = 1,
MinValue = 0,
MaxValue = 100,
};
[SettingSource("Opacity", "The receptor opacity.")]
public BindableFloat Opacity { get; } = new BindableFloat(1)
{
Precision = 0.1f,
MinValue = 0,
MaxValue = 1
};
private GridContainer gridContainer = null!;
public ManiaTouchInputArea(DrawableManiaRuleset drawableRuleset)
{
this.drawableRuleset = drawableRuleset;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
RelativeSizeAxes = Axes.Both;
Height = 0.5f;
}
[BackgroundDependencyLoader]
private void load()
{
List<Drawable> receptorGridContent = new List<Drawable>();
List<Dimension> receptorGridDimensions = new List<Dimension>();
bool first = true;
foreach (var stage in drawableRuleset.Playfield.Stages)
{
foreach (var column in stage.Columns)
{
if (!first)
{
receptorGridContent.Add(new Gutter { Spacing = { BindTarget = Spacing } });
receptorGridDimensions.Add(new Dimension(GridSizeMode.AutoSize));
}
receptorGridContent.Add(new ColumnInputReceptor
{
Action = { BindTarget = column.Action },
});
receptorGridDimensions.Add(new Dimension());
first = false;
}
}
InternalChild = gridContainer = new GridContainer
{
RelativeSizeAxes = Axes.Both,
AlwaysPresent = true,
Content = new[] { receptorGridContent.ToArray() },
ColumnDimensions = receptorGridDimensions.ToArray()
};
}
protected override void LoadComplete()
{
base.LoadComplete();
Opacity.BindValueChanged(o => Alpha = o.NewValue, true);
}
protected override bool OnKeyDown(KeyDownEvent e)
{
// Hide whenever the keyboard is used.
Hide();
return false;
}
protected override bool OnTouchDown(TouchDownEvent e)
{
Show();
return true;
}
protected override void PopIn()
{
gridContainer.FadeIn(500, Easing.OutQuint);
}
protected override void PopOut()
{
gridContainer.FadeOut(300);
}
public partial class ColumnInputReceptor : CompositeDrawable
{
public readonly IBindable<ManiaAction> Action = new Bindable<ManiaAction>();
private readonly Box highlightOverlay;
[Resolved]
private ManiaInputManager? inputManager { get; set; }
private bool isPressed;
public ColumnInputReceptor()
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 10,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.15f,
},
highlightOverlay = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
Blending = BlendingParameters.Additive,
}
}
}
};
}
protected override bool OnTouchDown(TouchDownEvent e)
{
updateButton(true);
return false; // handled by parent container to show overlay.
}
protected override void OnTouchUp(TouchUpEvent e)
{
updateButton(false);
}
private void updateButton(bool press)
{
if (press == isPressed)
return;
isPressed = press;
if (press)
{
inputManager?.KeyBindingContainer.TriggerPressed(Action.Value);
highlightOverlay.FadeTo(0.1f, 80, Easing.OutQuint);
}
else
{
inputManager?.KeyBindingContainer.TriggerReleased(Action.Value);
highlightOverlay.FadeTo(0, 400, Easing.OutQuint);
}
}
}
private partial class Gutter : Drawable
{
public readonly IBindable<float> Spacing = new Bindable<float>();
public Gutter()
{
Spacing.BindValueChanged(s => Size = new Vector2(s.NewValue));
}
}
}
}
@@ -13,6 +13,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public partial class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene
{
protected sealed override Ruleset CreateRuleset() => new OsuRuleset();
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
protected override HitObjectPlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
}
@@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public partial class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene
{
protected sealed override Ruleset CreateRuleset() => new OsuRuleset();
[SetUp]
public void Setup() => Schedule(() =>
{
@@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public partial class TestSceneSpinnerPlacementBlueprint : PlacementBlueprintTestScene
{
protected sealed override Ruleset CreateRuleset() => new OsuRuleset();
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
protected override HitObjectPlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
@@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
{
private const double difficulty_multiplier = 0.0675;
public override int Version => 20241007;
public override int Version => 20250306;
public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
@@ -3,6 +3,7 @@
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@@ -76,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
if (hasReachedObject && showHitMarkers.Value)
{
float alpha = Interpolation.ValueAt(editorTime, 0, 1f, hitObjectTime, hitObjectTime + FADE_OUT_EXTENSION, Easing.In);
float ringScale = MathHelper.Clamp(Interpolation.ValueAt(editorTime, 0, 1f, hitObjectTime, hitObjectTime + FADE_OUT_EXTENSION / 2, Easing.OutQuint), 0, 1);
float ringScale = Math.Clamp(Interpolation.ValueAt(editorTime, 0, 1f, hitObjectTime, hitObjectTime + FADE_OUT_EXTENSION / 2, Easing.OutQuint), 0, 1);
ring.Scale = new Vector2(1 + 0.1f * ringScale);
content.Alpha = 0.9f * (1 - alpha);
@@ -484,7 +484,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
// Snap the path to the current beat divisor before checking length validity.
hitObject.SnapTo(distanceSnapProvider);
if (!hitObject.Path.HasValidLength)
if (!hitObject.Path.HasValidLengthForPlacement)
{
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
hitObject.Path.ControlPoints[i].Position = oldControlPoints[i];
@@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };
protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;
protected override bool IsValidForPlacement => HitObject.Path.HasValidLengthForPlacement;
public SliderPlacementBlueprint()
: base(new Slider())
@@ -270,14 +270,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
if (adjustVelocity)
{
proposedVelocity = proposedDistance / oldDuration;
proposedDistance = MathHelper.Clamp(proposedDistance, 0.1 * oldDuration, 10 * oldDuration);
proposedDistance = Math.Clamp(proposedDistance, 0.1 * oldDuration, 10 * oldDuration);
}
else
{
double minDistance = distanceSnapProvider?.GetBeatSnapDistance() * oldVelocityMultiplier ?? 1;
// Add a small amount to the proposed distance to make it easier to snap to the full length of the slider.
proposedDistance = distanceSnapProvider?.FindSnappedDistance((float)proposedDistance + 1, HitObject.StartTime, HitObject) ?? proposedDistance;
proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance);
proposedDistance = Math.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance);
}
if (Precision.AlmostEquals(proposedDistance, HitObject.Path.Distance) && Precision.AlmostEquals(proposedVelocity, HitObject.SliderVelocityMultiplier))
@@ -476,7 +476,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
HitObject.SnapTo(distanceSnapProvider);
// If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLengthForPlacement)
{
placementHandler?.Delete(HitObject);
return;
@@ -626,7 +626,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
{
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos) && (IsSelected || DrawableObject.Body.Alpha > 0))
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos) && (IsSelected || DrawableObject.Body.Alpha > 0 || DrawableObject.HeadCircle.Alpha > 0))
return true;
if (ControlPointVisualiser == null)
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Edit
public BindableFloat Spacing { get; } = new BindableFloat(4f)
{
MinValue = 4f,
MaxValue = 128f,
MaxValue = 256f,
Precision = 0.01f,
};
@@ -30,6 +30,7 @@ using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Edit
{
@@ -351,6 +352,35 @@ namespace osu.Game.Rulesets.Osu.Edit
}
}
protected override bool OnMouseDown(MouseDownEvent e)
{
// Why is this logic here and not in `OsuSelectionHandler`?
// Because we only want to handle this toggle after all other right-click handling completes.
//
// Consider that input is handled from the most nested child first:
//
// ComposeScreen
// |- OsuContextMenuContainer // right click for context
// |- TimelineBlueprintContainer
// |- TimelineSelectionHandler
// |- (Osu)HitObjectComposer // right click for toggle new combo
// |- (Osu)EditorBlueprintContainer // right click for select
// |- (Osu)EditorSelectionHandler // right click for delete
if (e.Button == MouseButton.Right)
{
var osuSelectionHandler = (OsuSelectionHandler)BlueprintContainer.SelectionHandler;
if (!osuSelectionHandler.SelectedItems.Any())
{
osuSelectionHandler.SelectionNewComboState.Value =
osuSelectionHandler.SelectionNewComboState.Value == TernaryState.False ? TernaryState.True : TernaryState.False;
return true;
}
}
return base.OnMouseDown(e);
}
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Repeat)
@@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Osu.Edit
Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider });
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
if (xInBounds && yInBounds && slider.Path.HasValidLength)
if (xInBounds && yInBounds && slider.Path.HasValidLengthForPlacement)
return;
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
@@ -263,12 +263,12 @@ namespace osu.Game.Rulesets.Osu.Edit
{
case Axes.X:
(sLowerBound, sUpperBound) = computeBounds(lowerBounds - b, upperBounds - b, a);
s.X = MathHelper.Clamp(s.X, sLowerBound, sUpperBound);
s.X = Math.Clamp(s.X, sLowerBound, sUpperBound);
break;
case Axes.Y:
(sLowerBound, sUpperBound) = computeBounds(lowerBounds - a, upperBounds - a, b);
s.Y = MathHelper.Clamp(s.Y, sLowerBound, sUpperBound);
s.Y = Math.Clamp(s.Y, sLowerBound, sUpperBound);
break;
case Axes.Both:
@@ -276,11 +276,11 @@ namespace osu.Game.Rulesets.Osu.Edit
// Therefore the ratio s.X / s.Y will be maintained
(sLowerBound, sUpperBound) = computeBounds(lowerBounds, upperBounds, a * s.X + b * s.Y);
s.X = s.X < 0
? MathHelper.Clamp(s.X, s.X * sUpperBound, s.X * sLowerBound)
: MathHelper.Clamp(s.X, s.X * sLowerBound, s.X * sUpperBound);
? Math.Clamp(s.X, s.X * sUpperBound, s.X * sLowerBound)
: Math.Clamp(s.X, s.X * sLowerBound, s.X * sUpperBound);
s.Y = s.Y < 0
? MathHelper.Clamp(s.Y, s.Y * sUpperBound, s.Y * sLowerBound)
: MathHelper.Clamp(s.Y, s.Y * sLowerBound, s.Y * sUpperBound);
? Math.Clamp(s.Y, s.Y * sUpperBound, s.Y * sLowerBound)
: Math.Clamp(s.Y, s.Y * sLowerBound, s.Y * sUpperBound);
break;
}
@@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -127,8 +128,11 @@ namespace osu.Game.Rulesets.Osu.Edit
if (relativeCheckbox.Current.Value)
{
(xBindable.MinValue, xBindable.MaxValue) = (0 - initialSurroundingQuad.TopLeft.X, OsuPlayfield.BASE_SIZE.X - initialSurroundingQuad.BottomRight.X);
(yBindable.MinValue, yBindable.MaxValue) = (0 - initialSurroundingQuad.TopLeft.Y, OsuPlayfield.BASE_SIZE.Y - initialSurroundingQuad.BottomRight.Y);
xBindable.MinValue = 0 - Math.Max(initialSurroundingQuad.TopLeft.X, 0);
xBindable.MaxValue = OsuPlayfield.BASE_SIZE.X - Math.Min(initialSurroundingQuad.BottomRight.X, OsuPlayfield.BASE_SIZE.X);
yBindable.MinValue = 0 - Math.Max(initialSurroundingQuad.TopLeft.Y, 0);
yBindable.MaxValue = OsuPlayfield.BASE_SIZE.Y - Math.Min(initialSurroundingQuad.BottomRight.Y, OsuPlayfield.BASE_SIZE.Y);
xBindable.Default = yBindable.Default = 0;
@@ -146,8 +150,21 @@ namespace osu.Game.Rulesets.Osu.Edit
var quadRelativeToPosition = new RectangleF(initialSurroundingQuad.Location - initialPosition, initialSurroundingQuad.Size);
(xBindable.MinValue, xBindable.MaxValue) = (0 - quadRelativeToPosition.TopLeft.X, OsuPlayfield.BASE_SIZE.X - quadRelativeToPosition.BottomRight.X);
(yBindable.MinValue, yBindable.MaxValue) = (0 - quadRelativeToPosition.TopLeft.Y, OsuPlayfield.BASE_SIZE.Y - quadRelativeToPosition.BottomRight.Y);
if (initialSurroundingQuad.Width < OsuPlayfield.BASE_SIZE.X)
{
xBindable.MinValue = 0 - quadRelativeToPosition.TopLeft.X;
xBindable.MaxValue = OsuPlayfield.BASE_SIZE.X - quadRelativeToPosition.BottomRight.X;
}
else
xBindable.MinValue = xBindable.MaxValue = initialPosition.X;
if (initialSurroundingQuad.Height < OsuPlayfield.BASE_SIZE.Y)
{
yBindable.MinValue = 0 - quadRelativeToPosition.TopLeft.Y;
yBindable.MaxValue = OsuPlayfield.BASE_SIZE.Y - quadRelativeToPosition.BottomRight.Y;
}
else
yBindable.MinValue = yBindable.MaxValue = initialPosition.Y;
xBindable.Default = initialPosition.X;
yBindable.Default = initialPosition.Y;
@@ -3,9 +3,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Tests.Visual;
@@ -31,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Editor
AddStep("hover over first hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<DrawableHit>().ElementAt(1)));
AddStep("hover over second hit", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<DrawableHit>().ElementAt(0)));
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddUntilStep("context menu open", () => Editor.ChildrenOfType<OsuContextMenu>().Any(menu => menu.State == MenuState.Open));
AddUntilStep("second hit deleted", () => Editor.ChildrenOfType<DrawableHit>().Count(), () => Is.EqualTo(1));
}
}
}
@@ -13,25 +13,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
/// <summary>
/// The difficulty corresponding to the rhythm skill.
/// </summary>
[JsonProperty("rhythm_difficulty")]
public double RhythmDifficulty { get; set; }
/// <summary>
/// The difficulty corresponding to the reading skill.
/// </summary>
[JsonProperty("reading_difficulty")]
public double ReadingDifficulty { get; set; }
/// <summary>
/// The difficulty corresponding to the colour skill.
/// </summary>
[JsonProperty("colour_difficulty")]
public double ColourDifficulty { get; set; }
/// <summary>
/// The difficulty corresponding to the stamina skill.
/// </summary>
[JsonProperty("stamina_difficulty")]
public double StaminaDifficulty { get; set; }
/// <summary>
@@ -40,13 +36,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
[JsonProperty("mono_stamina_factor")]
public double MonoStaminaFactor { get; set; }
[JsonProperty("rhythm_difficult_strains")]
public double RhythmTopStrains { get; set; }
[JsonProperty("colour_difficult_strains")]
public double ColourTopStrains { get; set; }
[JsonProperty("stamina_difficult_strains")]
public double StaminaTopStrains { get; set; }
public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
private bool isConvert;
public override int Version => 20241007;
public override int Version => 20250306;
public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap)
: base(ruleset, beatmap)
@@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Taiko.Beatmaps;
@@ -21,9 +19,6 @@ namespace osu.Game.Rulesets.Taiko.UI
public readonly IBindable<bool> LockPlayfieldAspectRange = new BindableBool(true);
[Resolved]
private OsuGame? osuGame { get; set; }
public TaikoPlayfieldAdjustmentContainer()
{
RelativeSizeAxes = Axes.X;
@@ -60,19 +55,7 @@ namespace osu.Game.Rulesets.Taiko.UI
// Limit the maximum relative height of the playfield to one-third of available area to avoid it masking out on extreme resolutions.
relativeHeight = Math.Min(relativeHeight, 1f / 3f);
Scale = new Vector2(Math.Max((Parent!.ChildSize.Y / 768f) * (relativeHeight / base_relative_height), 1f));
// on mobile platforms where the base aspect ratio is wider, the taiko playfield
// needs to be scaled down to remain playable.
if (RuntimeInfo.IsMobile && osuGame != null)
{
const float base_aspect_ratio = 1024f / 768f;
float gameAspectRatio = osuGame.ScalingContainerTargetDrawSize.X / osuGame.ScalingContainerTargetDrawSize.Y;
// this magic scale is unexplainable, but required so the playfield doesn't become too zoomed out as the aspect ratio increases.
const float magic_scale = 1.25f;
Scale *= magic_scale * new Vector2(base_aspect_ratio / gameAspectRatio);
}
Scale = new Vector2(Parent!.ChildSize.Y / 768f * (relativeHeight / base_relative_height));
Width = 1 / Scale.X;
}
@@ -0,0 +1,58 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Tests.Beatmaps
{
public class BeatmapExtensionsTest
{
[Test]
public void TestLengthCalculations()
{
var beatmap = new Beatmap
{
HitObjects =
{
new HitCircle { StartTime = 5_000 },
new HitCircle { StartTime = 300_000 },
new Spinner { StartTime = 280_000, Duration = 40_000 }
},
Breaks =
{
new BreakPeriod(50_000, 75_000),
new BreakPeriod(100_000, 150_000),
}
};
Assert.That(beatmap.CalculatePlayableBounds(), Is.EqualTo((5_000, 320_000)));
Assert.That(beatmap.CalculatePlayableLength(), Is.EqualTo(315_000)); // 320_000 - 5_000
Assert.That(beatmap.CalculateDrainLength(), Is.EqualTo(240_000)); // 315_000 - (25_000 + 50_000) = 315_000 - 75_000
}
[Test]
public void TestDrainLengthCannotGoNegative()
{
var beatmap = new Beatmap
{
HitObjects =
{
new HitCircle { StartTime = 5_000 },
new HitCircle { StartTime = 300_000 },
new Spinner { StartTime = 280_000, Duration = 40_000 }
},
Breaks =
{
new BreakPeriod(0, 350_000),
}
};
Assert.That(beatmap.CalculatePlayableBounds(), Is.EqualTo((5_000, 320_000)));
Assert.That(beatmap.CalculatePlayableLength(), Is.EqualTo(315_000)); // 320_000 - 5_000
Assert.That(beatmap.CalculateDrainLength(), Is.EqualTo(0)); // break period encompasses entire beatmap
}
}
}
@@ -404,6 +404,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
}
}
[Test]
public void TestComboColourCountIsLimitedToEight()
{
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("too-many-combo-colours.osu"))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
Debug.Assert(comboColors != null);
Color4[] expectedColors =
{
new Color4(142, 199, 255, 255),
new Color4(255, 128, 128, 255),
new Color4(128, 255, 255, 255),
new Color4(128, 255, 128, 255),
new Color4(255, 187, 255, 255),
new Color4(255, 177, 140, 255),
new Color4(100, 100, 100, 255),
new Color4(142, 199, 255, 255),
};
Assert.AreEqual(expectedColors.Length, comboColors.Count);
for (int i = 0; i < expectedColors.Length; i++)
Assert.AreEqual(expectedColors[i], comboColors[i]);
}
}
[Test]
public void TestGetLastObjectTime()
{
@@ -28,6 +28,7 @@ using osu.Game.Skinning;
using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Beatmaps.Formats
{
@@ -184,6 +185,32 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5));
}
[Test]
public void TestOnlyEightComboColoursEncoded()
{
var beatmapSkin = new LegacyBeatmapSkin(new BeatmapInfo(), null)
{
Configuration =
{
CustomComboColours =
{
new Color4(1, 1, 1, 255),
new Color4(2, 2, 2, 255),
new Color4(3, 3, 3, 255),
new Color4(4, 4, 4, 255),
new Color4(5, 5, 5, 255),
new Color4(6, 6, 6, 255),
new Color4(7, 7, 7, 255),
new Color4(8, 8, 8, 255),
new Color4(9, 9, 9, 255),
}
}
};
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((new Beatmap(), beatmapSkin)), string.Empty);
Assert.That(decodedAfterEncode.skin.Configuration.CustomComboColours, Has.Count.EqualTo(8));
}
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
{
// equal to null, no need to SequenceEqual
@@ -212,6 +239,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
{
var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader);
var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name);
stream.Seek(0, SeekOrigin.Begin);
beatmapSkin.Configuration = new LegacySkinDecoder().Decode(reader);
return (convert(beatmap), beatmapSkin);
}
}
@@ -6,9 +6,9 @@ using Humanizer;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Game.Extensions;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Tests.Visual.Multiplayer;
namespace osu.Game.Tests.NonVisual.Multiplayer
@@ -16,6 +16,13 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
[HeadlessTest]
public partial class StatefulMultiplayerClientTest : MultiplayerTestScene
{
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
WaitForJoined();
}
[Test]
public void TestUserAddedOnJoin()
{
@@ -72,10 +79,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
AddStep("create room initially in gameplay", () =>
{
var newRoom = new Room();
newRoom.CopyFrom(SelectedRoom.Value!);
newRoom.RoomID = null;
MultiplayerClient.RoomSetupAction = room =>
{
room.State = MultiplayerRoomState.Playing;
@@ -86,13 +89,32 @@ namespace osu.Game.Tests.NonVisual.Multiplayer
});
};
RoomManager.CreateRoom(newRoom);
MultiplayerClient.JoinRoom(MultiplayerClient.ServerSideRooms.Single()).ConfigureAwait(false);
});
AddUntilStep("wait for room join", () => RoomJoined);
checkPlayingUserCount(1);
}
[Test]
public void TestJoinRoomWithManyUsers()
{
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
AddStep("create room with many users", () =>
{
MultiplayerClient.RoomSetupAction = room =>
{
room.Users.AddRange(Enumerable.Range(PLAYER_1_ID, 100).Select(id => new MultiplayerRoomUser(id)));
};
MultiplayerClient.JoinRoom(MultiplayerClient.ServerSideRooms.Single()).ConfigureAwait(false);
});
AddUntilStep("wait for room join", () => RoomJoined);
}
private void checkPlayingUserCount(int expectedCount)
=> AddAssert($"{"user".ToQuantity(expectedCount)} playing", () => MultiplayerClient.CurrentMatchPlayingUserIds.Count == expectedCount);
@@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.OnlinePlay
{
[HeadlessTest]
public partial class TestSceneOnlinePlaySubScreenStack : OnlinePlayTestScene
{
private ScreenStack stack = null!;
[SetUp]
public void Setup() => Schedule(() =>
{
Child = stack = new OnlinePlaySubScreenStack
{
RelativeSizeAxes = Axes.Both
};
});
[Test]
public void TestBindablesDisabledWhenRequested()
{
AddAssert("bindables not disabled", () => Beatmap.Disabled || Ruleset.Disabled || SelectedMods.Disabled, () => Is.False);
AddStep("push screen that disables bindables", () => stack.Push(new ScreenWithExternalBindableDisablement(true)));
AddAssert("bindables disabled", () => Beatmap.Disabled && Ruleset.Disabled && SelectedMods.Disabled, () => Is.True);
AddStep("push screen that does not disable bindables", () => stack.Push(new ScreenWithExternalBindableDisablement(false)));
AddAssert("bindables not disabled", () => Beatmap.Disabled || Ruleset.Disabled || SelectedMods.Disabled, () => Is.False);
AddStep("exit one screen", () => stack.Exit());
AddAssert("bindables disabled", () => Beatmap.Disabled && Ruleset.Disabled && SelectedMods.Disabled, () => Is.True);
}
[Test]
public void TestModsResetWhenExitToLounge()
{
AddStep("push lounge", () => stack.Push(new PlaylistsLoungeSubScreen()));
AddStep("push screen with mod", () => stack.Push(new ScreenWithMod(new OsuModDoubleTime())));
AddUntilStep("wait for screen to load", () => ((OsuScreen)stack.CurrentScreen).IsLoaded);
AddAssert("mod set", () => SelectedMods.Value.Count, () => Is.GreaterThan(0));
AddStep("exit to lounge", () => stack.Exit());
AddAssert("mods reset", () => SelectedMods.Value.Count, () => Is.Zero);
}
private partial class ScreenWithExternalBindableDisablement : OsuScreen
{
public override bool DisallowExternalBeatmapRulesetChanges { get; }
public ScreenWithExternalBindableDisablement(bool disableBindables)
{
DisallowExternalBeatmapRulesetChanges = disableBindables;
}
}
private partial class ScreenWithMod : OsuScreen
{
private readonly Mod mod;
public ScreenWithMod(Mod mod)
{
this.mod = mod;
}
protected override void LoadComplete()
{
base.LoadComplete();
Mods.Value = [mod];
}
}
}
}
@@ -0,0 +1,73 @@
osu file format v14
[General]
AudioFilename: 03. Renatus - Soleily 192kbps.mp3
AudioLeadIn: 0
PreviewTime: 164471
Countdown: 0
SampleSet: Soft
StackLeniency: 0.7
Mode: 0
LetterboxInBreaks: 0
WidescreenStoryboard: 0
[Editor]
Bookmarks: 11505,22054,32604,43153,53703,64252,74802,85351,95901,106450,116999,119637,130186,140735,151285,161834,164471,175020,185570,196119,206669,209306
DistanceSpacing: 1.8
BeatDivisor: 4
GridSize: 4
TimelineZoom: 2
[Metadata]
Title:Renatus
TitleUnicode:Renatus
Artist:Soleily
ArtistUnicode:Soleily
Creator:Gamu
Version:Insane
Source:
Tags:MBC7 Unisphere 地球ヤバイEP Chikyu Yabai
BeatmapID:557821
BeatmapSetID:241526
[Difficulty]
HPDrainRate:6.5
CircleSize:4
OverallDifficulty:8
ApproachRate:9
SliderMultiplier:1.8
SliderTickRate:2
[Events]
//Background and Video events
0,0,"machinetop_background.jpg",0,0
//Break Periods
2,122474,140135
//Storyboard Layer 0 (Background)
//Storyboard Layer 1 (Fail)
//Storyboard Layer 2 (Pass)
//Storyboard Layer 3 (Foreground)
//Storyboard Sound Samples
[TimingPoints]
956,329.67032967033,4,2,0,60,1,0
[Colours]
Combo1:142,199,255
Combo2:255,128,128
Combo3:128,255,255
Combo4:128,255,128
Combo5:255,187,255
Combo6:255,177,140
Combo7:100,100,100
Combo8:142,199,255
Combo9:255,128,128
Combo10:128,255,255
Combo11:128,255,128
Combo12:255,187,255
Combo13:255,177,140
Combo14:100,100,100
[HitObjects]
192,168,956,6,0,P|184:128|200:80,1,90,4|0,1:2|0:0,0:0:0:0:
@@ -6,6 +6,8 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.Metadata;
@@ -13,9 +15,11 @@ using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.SelectV2.Leaderboards;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
namespace osu.Game.Tests.Visual.DailyChallenge
{
@@ -57,6 +61,39 @@ namespace osu.Game.Tests.Visual.DailyChallenge
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
}
[Test]
public void TestUseTheseModsUnavailableIfNoFreeMods()
{
var room = new Room
{
RoomID = 1234,
Name = "Daily Challenge: June 4, 2024",
Playlist =
[
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
{
RequiredMods = [new APIMod(new OsuModTraceable())],
AllowedMods = []
}
],
EndDate = DateTimeOffset.Now.AddHours(12),
Category = RoomCategory.DailyChallenge
};
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
AddUntilStep("wait for pushed", () => screen.IsCurrentScreen());
AddStep("force transforms to finish", () => FinishTransforms(true));
AddStep("right click second score", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<LeaderboardScoreV2>().ElementAt(1));
InputManager.Click(MouseButton.Right);
});
AddAssert("use these mods not present",
() => this.ChildrenOfType<OsuContextMenu>().All(m => m.Items.All(item => item.Text.Value != "Use these mods")));
}
[Test]
public void TestNotifications()
{
@@ -94,6 +94,8 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.AsNonNull().ID)?.Value.DeletePending == true);
AddUntilStep("wait for default beatmap", () => Editor.Beatmap.Value is DummyWorkingBeatmap);
}
[Test]
@@ -171,6 +173,8 @@ namespace osu.Game.Tests.Visual.Editing
return difficultyName != null && difficultyName != firstDifficultyName;
});
ensureEditorLoaded();
AddAssert("created difficulty has timing point", () =>
{
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
@@ -215,6 +219,8 @@ namespace osu.Game.Tests.Visual.Editing
return difficultyName != null && difficultyName != previousDifficultyName;
});
ensureEditorLoaded();
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString());
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add effect points", () =>
@@ -239,6 +245,8 @@ namespace osu.Game.Tests.Visual.Editing
return difficultyName != null && difficultyName != previousDifficultyName;
});
ensureEditorLoaded();
AddAssert("created difficulty has timing point", () =>
{
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
@@ -287,6 +295,8 @@ namespace osu.Game.Tests.Visual.Editing
return difficultyName != null && difficultyName != firstDifficultyName;
});
ensureEditorLoaded();
AddAssert("created difficulty has timing point", () =>
{
var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single();
@@ -367,6 +377,8 @@ namespace osu.Game.Tests.Visual.Editing
return difficultyName != null && difficultyName != originalDifficultyName;
});
ensureEditorLoaded();
AddAssert("created difficulty has copy suffix in name", () => EditorBeatmap.BeatmapInfo.DifficultyName == copyDifficultyName);
AddAssert("created difficulty has timing point", () =>
{
@@ -377,7 +389,9 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4);
AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2);
ensureEditorLoaded();
AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
AddAssert("online ID not copied", () => EditorBeatmap.BeatmapInfo.OnlineID == -1);
AddStep("save beatmap", () => Editor.Save());
@@ -440,6 +454,8 @@ namespace osu.Game.Tests.Visual.Editing
return difficultyName != null && difficultyName != originalDifficultyName;
});
ensureEditorLoaded();
AddStep("save without changes", () => Editor.Save());
AddAssert("collection still points to old beatmap", () => !collection.BeatmapMD5Hashes.Contains(EditorBeatmap.BeatmapInfo.MD5Hash)
@@ -477,6 +493,9 @@ namespace osu.Game.Tests.Visual.Editing
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName != "New Difficulty";
});
ensureEditorLoaded();
AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)");
AddAssert("new difficulty persisted", () =>
{
@@ -514,6 +533,8 @@ namespace osu.Game.Tests.Visual.Editing
return difficultyName != null && difficultyName != duplicate_difficulty_name;
});
ensureEditorLoaded();
AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = duplicate_difficulty_name);
AddStep("try to save beatmap", () => Editor.Save());
AddAssert("beatmap set not corrupted", () =>
@@ -540,6 +561,8 @@ namespace osu.Game.Tests.Visual.Editing
return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1);
});
ensureEditorLoaded();
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new CatchRuleset().RulesetInfo));
AddUntilStep("wait for created", () =>
@@ -547,7 +570,8 @@ namespace osu.Game.Tests.Visual.Editing
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName != duplicate_difficulty_name;
});
AddUntilStep("wait for editor load", () => Editor.IsLoaded && DialogOverlay.IsLoaded);
ensureEditorLoaded();
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
{
@@ -584,6 +608,9 @@ namespace osu.Game.Tests.Visual.Editing
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName == "New Difficulty";
});
ensureEditorLoaded();
AddAssert("new difficulty persisted", () =>
{
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
@@ -610,6 +637,9 @@ namespace osu.Game.Tests.Visual.Editing
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName == "New Difficulty (1)";
});
ensureEditorLoaded();
AddAssert("new difficulty persisted", () =>
{
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
@@ -735,6 +765,8 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3"));
}
private void ensureEditorLoaded() => AddUntilStep("wait for editor load", () => Editor.IsLoaded && DialogOverlay.IsLoaded);
private void createNewDifficulty()
{
string? currentDifficulty = null;
@@ -748,13 +780,14 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction());
AddUntilStep("wait for created", () =>
{
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName != currentDifficulty;
});
ensureEditorLoaded();
AddUntilStep("wait for editor load", () => Editor.IsLoaded);
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
}
@@ -765,7 +798,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep($"switch to difficulty #{index + 1}", () =>
Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(index)));
AddUntilStep("wait for editor load", () => Editor.IsLoaded);
ensureEditorLoaded();
AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup);
AddUntilStep("wait for load", () => Editor.ChildrenOfType<SetupScreen>().Any());
}
@@ -7,6 +7,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit;
@@ -14,8 +15,10 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
@@ -58,19 +61,63 @@ namespace osu.Game.Tests.Visual.Editing
}
[Test]
public void TestContextMenu()
public void TestRightClickDuringEmptyPlacementTogglesNewCombo()
{
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
AddStep("move mouse away from placed circle", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single().ScreenSpaceDrawQuad.TopLeft + Vector2.One));
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
AddAssert("new combo true", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.True));
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
}
[Test]
public void TestRightClickDuringPlacementDeletes()
{
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Has.Exactly(0).Items);
AddAssert("circle not selected", () => EditorBeatmap.SelectedHitObjects, () => Has.Exactly(0).Items);
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
}
[Test]
public void TestRightClickDuringSelectionShowsContextMenu()
{
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
AddStep("delete with right mouse", () =>
{
InputManager.Click(MouseButton.Right);
});
AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.One.Items);
// ensure the circle we're selecting is not a new combo so we can assert
// new combo doesn't happen to get toggled by right click.
AddStep("seek forward", () => EditorClock.Seek(1000));
AddStep("place second circle", () => InputManager.Click(MouseButton.Left));
AddAssert("two circles added", () => EditorBeatmap.HitObjects, () => Has.Exactly(2).Items);
AddAssert("context menu not visible", () => !Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
AddStep("select selection tool", () => InputManager.Key(Key.Number1));
AddStep("click right mouse", () => InputManager.Click(MouseButton.Right));
AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.Exactly(2).Items);
AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items);
AddAssert("context menu visible", () => Editor.ChildrenOfType<OsuContextMenu>().Any(c => c.IsPresent));
AddAssert("new combo false", () => this.ChildrenOfType<NewComboTernaryButton>().Single().Current.Value, () => Is.EqualTo(TernaryState.False));
}
[Test]
@@ -128,12 +128,12 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("Press alt down", () => InputManager.PressKey(Key.AltLeft));
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(0, 3)));
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
AddAssert("Box 1/2 at 1/2", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.5f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.5f * scrollQuad.Size.X));
// Scroll out at 0.25
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(0, -3)));
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
AddAssert("Box 1/2 at 1/2", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.5f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.5f * scrollQuad.Size.X));
AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft));
}
@@ -40,11 +40,16 @@ namespace osu.Game.Tests.Visual.Gameplay
RelativeSizeAxes = Axes.Both,
},
breakTracker = new TestBreakTracker(),
breakOverlay = new BreakOverlay(true, new ScoreProcessor(new OsuRuleset()))
breakOverlay = new BreakOverlay(new ScoreProcessor(new OsuRuleset()))
{
ProcessCustomClock = false,
BreakTracker = breakTracker,
}
},
new LetterboxOverlay
{
ProcessCustomClock = false,
BreakTracker = breakTracker,
},
};
}
@@ -1,24 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.Shapes;
using osu.Game.Screens.Play.Break;
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneLetterboxOverlay : OsuTestScene
{
public TestSceneLetterboxOverlay()
{
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both
},
new LetterboxOverlay()
});
}
}
}
@@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
Bindable<LocalUserPlayingState> playingState = new Bindable<LocalUserPlayingState>();
GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), healthProcessor: new OsuHealthProcessor(0), localUserPlayingState: playingState);
TestSpectatorClient spectatorClient = new TestSpectatorClient();
TestMultiplayerClient multiplayerClient = new TestMultiplayerClient(new TestMultiplayerRoomManager(new TestRoomRequestsHandler()));
TestMultiplayerClient multiplayerClient = new TestMultiplayerClient(new TestRoomRequestsHandler());
AddStep("create spectator list", () =>
{
@@ -75,8 +75,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRepeatStep("remove random user", () => ((ISpectatorClient)spectatorClient).UserEndedWatching(
spectatorClient.WatchingUsers[RNG.Next(spectatorClient.WatchingUsers.Count)].OnlineID), 5);
AddStep("change font to venera", () => list.Font.Value = Typeface.Venera);
AddStep("change font to torus", () => list.Font.Value = Typeface.Torus);
AddStep("change font to venera", () => list.HeaderFont.Value = Typeface.Venera);
AddStep("change font to torus", () => list.HeaderFont.Value = Typeface.Torus);
AddStep("change header colour", () => list.HeaderColour.Value = new Colour4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1));
AddStep("enter break", () => playingState.Value = LocalUserPlayingState.Break);
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader]
private void load()
{
var mockLounge = new Mock<LoungeSubScreen>();
var mockLounge = new Mock<IOnlinePlayLounge>();
mockLounge
.Setup(l => l.Join(It.IsAny<Room>(), It.IsAny<string>(), It.IsAny<Action<Room>>(), It.IsAny<Action<string>>()))
.Callback<Room, string, Action<Room>, Action<string>>((_, _, _, d) =>
@@ -15,6 +15,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneDrawableRoomParticipantsList : OnlinePlayTestScene
{
private Room room = null!;
private DrawableRoomParticipantsList list = null!;
public override void SetUpSteps()
@@ -23,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create list", () =>
{
SelectedRoom.Value = new Room
room = new Room
{
Name = "test room",
Host = new APIUser
@@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
};
Child = list = new DrawableRoomParticipantsList(SelectedRoom.Value)
Child = list = new DrawableRoomParticipantsList(room)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -119,7 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("4 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 4);
AddAssert("46 hidden users", () => list.ChildrenOfType<DrawableRoomParticipantsList.HiddenUserCount>().Single().Count == 46);
AddStep("remove from end", () => removeUserAt(SelectedRoom.Value!.RecentParticipants.Count - 1));
AddStep("remove from end", () => removeUserAt(room.RecentParticipants.Count - 1));
AddAssert("4 circles displayed", () => list.ChildrenOfType<UpdateableAvatar>().Count() == 4);
AddAssert("45 hidden users", () => list.ChildrenOfType<DrawableRoomParticipantsList.HiddenUserCount>().Single().Count == 45);
@@ -138,18 +139,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void addUser(int id)
{
SelectedRoom.Value!.RecentParticipants = SelectedRoom.Value!.RecentParticipants.Append(new APIUser
room.RecentParticipants = room.RecentParticipants.Append(new APIUser
{
Id = id,
Username = $"User {id}"
}).ToArray();
SelectedRoom.Value!.ParticipantCount++;
room.ParticipantCount++;
}
private void removeUserAt(int index)
{
SelectedRoom.Value!.RecentParticipants = SelectedRoom.Value!.RecentParticipants.Where(u => !u.Equals(SelectedRoom.Value!.RecentParticipants[index])).ToArray();
SelectedRoom.Value!.ParticipantCount--;
room.RecentParticipants = room.RecentParticipants.Where(u => !u.Equals(room.RecentParticipants[index])).ToArray();
room.ParticipantCount--;
}
}
}
@@ -16,15 +16,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene
{
private Room room = null!;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("create area", () =>
{
SelectedRoom.Value = new Room();
Child = new MatchBeatmapDetailArea(SelectedRoom.Value)
Child = new MatchBeatmapDetailArea(room = new Room())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -36,9 +36,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createNewItem()
{
SelectedRoom.Value!.Playlist = SelectedRoom.Value.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
room.Playlist = room.Playlist.Append(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
ID = SelectedRoom.Value.Playlist.Count,
ID = room.Playlist.Count,
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
@@ -61,9 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create leaderboard", () =>
{
SelectedRoom.Value = new Room { RoomID = 3 };
Child = new MatchLeaderboard(SelectedRoom.Value)
Child = new MatchLeaderboard(new Room { RoomID = 3 })
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
@@ -24,6 +24,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
base.SetUpSteps();
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
WaitForJoined();
AddStep("reset", () =>
{
leaderboard?.RemoveAndDisposeImmediately();
@@ -17,6 +17,7 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
@@ -42,6 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private BeatmapManager beatmapManager { get; set; } = null!;
private MultiSpectatorScreen spectatorScreen = null!;
private Room room = null!;
private readonly List<MultiplayerRoomUser> playingUsers = new List<MultiplayerRoomUser>();
@@ -63,6 +65,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
base.SetUpSteps();
AddStep("clear playing users", () => playingUsers.Clear());
AddStep("create room", () => room = CreateDefaultRoom());
AddStep("join room", () => JoinRoom(room));
WaitForJoined();
}
[TestCase(1)]
@@ -456,7 +462,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
applyToBeatmap?.Invoke(Beatmap.Value);
LoadScreen(spectatorScreen = new MultiSpectatorScreen(SelectedRoom.Value!, playingUsers.ToArray()));
LoadScreen(spectatorScreen = new MultiSpectatorScreen(room, playingUsers.ToArray()));
});
AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded && (!waitForPlayerLoad || spectatorScreen.AllPlayersLoaded));
@@ -33,7 +33,6 @@ using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay;
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;
@@ -58,7 +57,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayerComponents multiplayerComponents = null!;
private TestMultiplayerClient multiplayerClient => multiplayerComponents.MultiplayerClient;
private TestMultiplayerRoomManager roomManager => multiplayerComponents.RoomManager;
[Resolved]
private OsuConfigManager config { get; set; } = null!;
@@ -257,7 +255,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
Playlist =
@@ -286,7 +284,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
Playlist =
@@ -336,7 +334,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
Password = "password",
@@ -789,7 +787,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create room", () =>
{
roomManager.AddServerSideRoom(new Room
multiplayerClient.AddServerSideRoom(new Room
{
Name = "Test Room",
QueueMode = QueueMode.AllPlayers,
@@ -807,11 +805,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("disable polling", () => this.ChildrenOfType<ListingPollingComponent>().Single().TimeBetweenPolls.Value = 0);
AddStep("disable polling", () => this.ChildrenOfType<LoungeListingPoller>().Single().TimeBetweenPolls.Value = 0);
AddStep("change server-side settings", () =>
{
roomManager.ServerSideRooms[0].Name = "New name";
roomManager.ServerSideRooms[0].Playlist =
multiplayerClient.ServerSideRooms[0].Name = "New name";
multiplayerClient.ServerSideRooms[0].Playlist =
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
@@ -828,7 +826,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("local room has correct settings", () =>
{
var localRoom = this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().Room;
return localRoom.Name == roomManager.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2;
return localRoom.Name == multiplayerClient.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2;
});
}
@@ -16,45 +16,32 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
public partial class TestSceneMultiplayerLoungeSubScreen : MultiplayerTestScene
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private LoungeSubScreen loungeScreen = null!;
private Room? lastJoinedRoom;
private string? lastJoinedPassword;
private MultiplayerLoungeSubScreen loungeScreen = null!;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("push screen", () => LoadScreen(loungeScreen = new MultiplayerLoungeSubScreen()));
AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen());
AddStep("bind to event", () =>
{
lastJoinedRoom = null;
lastJoinedPassword = null;
RoomManager.JoinRoomRequested = onRoomJoined;
});
}
[Test]
public void TestJoinRoomWithoutPassword()
{
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: false));
createRooms(GenerateRooms(1, withPassword: false));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("join room", () => InputManager.Key(Key.Enter));
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
AddAssert("room join password correct", () => lastJoinedPassword == null);
AddAssert("room joined", () => MultiplayerClient.RoomJoined);
}
[Test]
public void TestPopoverHidesOnBackButton()
{
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
createRooms(GenerateRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
@@ -67,18 +54,22 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("hit escape", () => InputManager.Key(Key.Escape));
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
public void TestPopoverHidesOnLeavingScreen()
{
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
createRooms(GenerateRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddStep("exit screen", () => Stack.Exit());
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
@@ -86,16 +77,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
createRooms(GenerateRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
AddAssert("still at lounge", () => loungeScreen.IsCurrentScreen());
AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible);
AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
@@ -103,16 +96,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
createRooms(GenerateRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddAssert("room not joined", () => loungeScreen.IsCurrentScreen());
AddAssert("still at lounge", () => loungeScreen.IsCurrentScreen());
AddUntilStep("password prompt still visible", () => passwordEntryPopover!.State.Value == Visibility.Visible);
AddAssert("textbox still focused", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
}
[Test]
@@ -120,15 +115,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
createRooms(GenerateRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
AddAssert("room join password correct", () => lastJoinedPassword == "password");
AddUntilStep("room joined", () => MultiplayerClient.RoomJoined);
}
[Test]
@@ -136,21 +130,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
createRooms(GenerateRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
AddAssert("room join password correct", () => lastJoinedPassword == "password");
AddAssert("room joined", () => MultiplayerClient.RoomJoined);
}
private void onRoomJoined(Room room, string? password)
private void createRooms(params Room[] rooms)
{
lastJoinedRoom = room;
lastJoinedPassword = password;
AddStep("create rooms", () =>
{
foreach (var room in rooms)
API.Queue(new CreateRoomRequest(room));
});
AddStep("refresh lounge", () => loungeScreen.RefreshRooms());
}
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new MultiplayerTestSceneDependencies();
}
}
@@ -39,6 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private TestMultiplayerMatchSongSelect songSelect = null!;
private Live<BeatmapSetInfo> importedBeatmapSet = null!;
private Room room = null!;
[Resolved]
private OsuConfigManager configManager { get; set; } = null!;
@@ -58,6 +59,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
Add(beatmapStore);
}
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("create room", () => room = CreateDefaultRoom());
AddStep("join room", () => JoinRoom(room));
WaitForJoined();
}
private void setUp()
{
AddStep("create song select", () =>
@@ -66,7 +76,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Beatmap.SetDefault();
SelectedMods.SetDefault();
LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value!));
LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(room));
});
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
@@ -138,8 +148,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create song select", () =>
{
SelectedRoom.Value!.Playlist.Single().RulesetID = 2;
songSelect = new TestMultiplayerMatchSongSelect(SelectedRoom.Value, SelectedRoom.Value.Playlist.Single());
room.Playlist.Single().RulesetID = 2;
songSelect = new TestMultiplayerMatchSongSelect(room, room.Playlist.Single());
songSelect.OnLoadComplete += _ => Ruleset.Value = new TaikoRuleset().RulesetInfo;
LoadScreen(songSelect);
});
@@ -43,11 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private MultiplayerMatchSubScreen screen = null!;
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
public TestSceneMultiplayerMatchSubScreen()
: base(false)
{
}
private Room room = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -66,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("load match", () =>
{
SelectedRoom.Value = new Room { Name = "Test Room" };
LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value!));
room = new Room { Name = "Test Room" };
LoadScreen(screen = new TestMultiplayerMatchSubScreen(room));
});
AddUntilStep("wait for load", () => screen.IsCurrentScreen());
@@ -78,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
@@ -97,7 +93,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo)
{
@@ -122,7 +118,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set playlist", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
@@ -139,7 +135,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
{
@@ -170,7 +166,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item with allowed mod", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
@@ -199,7 +195,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item with allowed mod", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
@@ -223,7 +219,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item with no allowed mods", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
@@ -246,7 +242,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add two playlist items", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
{
@@ -285,7 +281,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value!.Playlist =
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
@@ -317,6 +313,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
}
[Test]
public void TestChangeSettingsButtonVisibleForHost()
{
AddStep("add playlist item", () =>
{
room.Playlist =
[
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
];
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomJoined);
AddUntilStep("button visible", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.GreaterThan(0));
AddStep("join other user", void () => MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }));
AddStep("make other user host", () => MultiplayerClient.TransferHost(PLAYER_1_ID));
AddAssert("button hidden", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.EqualTo(0));
}
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
{
[Resolved(canBeNull: true)]
@@ -28,9 +28,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerParticipantsList : MultiplayerTestScene
{
[SetUpSteps]
public void SetupSteps()
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
WaitForJoined();
createNewParticipantsList();
}
@@ -22,6 +22,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerPlayer player = null!;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
WaitForJoined();
}
[Test]
public void TestGameplay()
{
@@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
private BeatmapInfo importedBeatmap = null!;
private Room room = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -46,9 +47,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
base.SetUpSteps();
AddStep("create room", () => room = CreateDefaultRoom());
AddStep("join room", () => JoinRoom(room));
WaitForJoined();
AddStep("create list", () =>
{
Child = list = new MultiplayerPlaylist(SelectedRoom.Value!)
Child = list = new MultiplayerPlaylist(room)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -127,7 +132,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
addItemStep();
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
AddStep("leave room", () => RoomManager.PartRoom());
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
AddUntilStep("item 0 not in lists", () => !inHistoryList(0) && !inQueueList(0));
@@ -148,7 +153,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
assertQueueTabCount(2);
AddStep("leave room", () => RoomManager.PartRoom());
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
assertQueueTabCount(0);
}
@@ -157,12 +162,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestJoinRoomWithMixedItemsAddedInCorrectLists()
{
AddStep("leave room", () => RoomManager.PartRoom());
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
AddUntilStep("wait for room part", () => !RoomJoined);
AddStep("join room with items", () =>
{
RoomManager.CreateRoom(new Room
API.Queue(new CreateRoomRequest(new Room
{
Name = "test name",
Playlist =
@@ -177,7 +182,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
Expired = true
}
]
});
}));
});
AddUntilStep("wait for room join", () => RoomJoined);
@@ -29,6 +29,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
private BeatmapInfo importedBeatmap = null!;
private Room room = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -42,9 +43,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
base.SetUpSteps();
AddStep("create room", () => room = CreateDefaultRoom());
AddStep("join room", () => JoinRoom(room));
WaitForJoined();
AddStep("create playlist", () =>
{
Child = playlist = new MultiplayerQueueList(SelectedRoom.Value!)
Child = playlist = new MultiplayerQueueList(room)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private MultiplayerSpectateButton spectateButton = null!;
private MatchStartControl startControl = null!;
private Room room = null!;
private BeatmapSetInfo importedSet = null!;
private BeatmapManager beatmaps = null!;
@@ -46,11 +47,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
base.SetUpSteps();
AddStep("create room", () => room = CreateDefaultRoom());
AddStep("join room", () => JoinRoom(room));
WaitForJoined();
AddStep("create button", () =>
{
PlaylistItem item = SelectedRoom.Value!.Playlist.First();
AvailabilityTracker.SelectedItem.Value = item;
AvailabilityTracker.SelectedItem.Value = room.Playlist.First();
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
@@ -69,14 +72,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
SelectedItem = new Bindable<PlaylistItem?>(item)
SelectedItem = new Bindable<PlaylistItem?>(room.Playlist.First())
},
startControl = new MatchStartControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200, 50),
SelectedItem = new Bindable<PlaylistItem?>(item)
SelectedItem = new Bindable<PlaylistItem?>(room.Playlist.First())
}
}
}
@@ -27,6 +27,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
private BeatmapManager manager = null!;
private TestPlaylistsSongSelect songSelect = null!;
private Room room = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -51,13 +52,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("reset", () =>
{
SelectedRoom.Value = new Room();
room = new Room();
Ruleset.Value = new OsuRuleset().RulesetInfo;
Beatmap.SetDefault();
SelectedMods.Value = Array.Empty<Mod>();
});
AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(SelectedRoom.Value!)));
AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(room)));
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
}
@@ -65,14 +66,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestItemAddedIfEmptyOnStart()
{
AddStep("finalise selection", () => songSelect.FinaliseSelection());
AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1);
AddAssert("playlist has 1 item", () => room.Playlist.Count == 1);
}
[Test]
public void TestItemAddedWhenCreateNewItemClicked()
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1);
AddAssert("playlist has 1 item", () => room.Playlist.Count == 1);
}
[Test]
@@ -80,7 +81,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("finalise selection", () => songSelect.FinaliseSelection());
AddAssert("playlist has 1 item", () => SelectedRoom.Value!.Playlist.Count == 1);
AddAssert("playlist has 1 item", () => room.Playlist.Count == 1);
}
[Test]
@@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("playlist has 2 items", () => SelectedRoom.Value!.Playlist.Count == 2);
AddAssert("playlist has 2 items", () => room.Playlist.Count == 2);
}
[Test]
@@ -96,10 +97,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddStep("rearrange", () => SelectedRoom.Value!.Playlist = SelectedRoom.Value!.Playlist.Skip(1).Append(SelectedRoom.Value!.Playlist[0]).ToArray());
AddStep("rearrange", () => room.Playlist = room.Playlist.Skip(1).Append(room.Playlist[0]).ToArray());
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
AddAssert("new item has id 2", () => SelectedRoom.Value!.Playlist.Last().ID == 2);
AddAssert("new item has id 2", () => room.Playlist.Last().ID == 2);
}
/// <summary>
@@ -115,13 +116,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("item 1 has rate 1.5", () =>
{
var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
var mod = (OsuModDoubleTime)room.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
return Precision.AlmostEquals(1.5, mod.SpeedChange.Value);
});
AddAssert("item 2 has rate 2", () =>
{
var mod = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset());
var mod = (OsuModDoubleTime)room.Playlist.Last().RequiredMods[0].ToMod(new OsuRuleset());
return Precision.AlmostEquals(2, mod.SpeedChange.Value);
});
}
@@ -147,7 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
AddAssert("item has rate 1.5", () =>
{
var m = (OsuModDoubleTime)SelectedRoom.Value!.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
var m = (OsuModDoubleTime)room.Playlist.First().RequiredMods[0].ToMod(new OsuRuleset());
return Precision.AlmostEquals(1.5, m.SpeedChange.Value);
});
}
@@ -3,6 +3,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
@@ -10,6 +11,7 @@ using osu.Framework.Testing;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Taiko;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Tests.Visual.OnlinePlay;
@@ -17,11 +19,11 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneLoungeRoomsContainer : OnlinePlayTestScene
public partial class TestSceneRoomListing : OnlinePlayTestScene
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private RoomsContainer container = null!;
private BindableList<Room> rooms = null!;
private IBindable<Room?> selectedRoom = null!;
private RoomListing container = null!;
public override void SetUpSteps()
{
@@ -29,17 +31,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("create container", () =>
{
rooms = new BindableList<Room>();
selectedRoom = new Bindable<Room?>();
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
Child = container = new RoomsContainer
Child = container = new RoomListing
{
SelectedRoom = { BindTarget = SelectedRoom }
RelativeSizeAxes = Axes.Both,
Rooms = { BindTarget = rooms },
SelectedRoom = { BindTarget = selectedRoom }
}
};
});
@@ -48,57 +53,58 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestBasicListChanges()
{
AddStep("add rooms", () => RoomManager.AddRooms(5, withSpotlightRooms: true));
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(5, withSpotlightRooms: true)));
AddAssert("has 5 rooms", () => container.Rooms.Count == 5);
AddAssert("has 5 rooms", () => container.DrawableRooms.Count == 5);
AddAssert("all spotlights at top", () => container.Rooms
AddAssert("all spotlights at top", () => container.DrawableRooms
.SkipWhile(r => r.Room.Category == RoomCategory.Spotlight)
.All(r => r.Room.Category == RoomCategory.Normal));
AddStep("remove first room", () => RoomManager.RemoveRoom(RoomManager.Rooms.First(r => r.RoomID == 0)));
AddAssert("has 4 rooms", () => container.Rooms.Count == 4);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID != 0));
AddStep("remove first room", () => rooms.RemoveAt(0));
AddAssert("has 4 rooms", () => container.DrawableRooms.Count == 4);
AddAssert("first room removed", () => container.DrawableRooms.All(r => r.Room.RoomID != 0));
AddStep("select first room", () => container.Rooms.First().TriggerClick());
AddAssert("first spotlight selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight)));
AddStep("select first room", () => container.DrawableRooms.First().TriggerClick());
AddAssert("first spotlight selected", () => checkRoomSelected(rooms.First(r => r.Category == RoomCategory.Spotlight)));
AddStep("remove last room", () => RoomManager.RemoveRoom(RoomManager.Rooms.MinBy(r => r.RoomID)!));
AddAssert("first spotlight still selected", () => checkRoomSelected(RoomManager.Rooms.First(r => r.Category == RoomCategory.Spotlight)));
AddStep("remove last room", () => rooms.RemoveAt(rooms.Count - 1));
AddAssert("first spotlight still selected", () => checkRoomSelected(rooms.First(r => r.Category == RoomCategory.Spotlight)));
AddStep("remove spotlight room", () => RoomManager.RemoveRoom(RoomManager.Rooms.Single(r => r.Category == RoomCategory.Spotlight)));
AddStep("remove spotlight room", () => rooms.RemoveAll(r => r.Category == RoomCategory.Spotlight));
AddAssert("selection vacated", () => checkRoomSelected(null));
}
[Test]
public void TestKeyboardNavigation()
{
AddStep("add rooms", () => RoomManager.AddRooms(3));
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(3)));
AddAssert("no selection", () => checkRoomSelected(null));
press(Key.Down);
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
AddAssert("first room selected", () => checkRoomSelected(container.DrawableRooms.First().Room));
press(Key.Up);
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
AddAssert("first room selected", () => checkRoomSelected(container.DrawableRooms.First().Room));
press(Key.Down);
press(Key.Down);
AddAssert("last room selected", () => checkRoomSelected(RoomManager.Rooms.Last()));
AddAssert("last room selected", () => checkRoomSelected(container.DrawableRooms.Last().Room));
}
[Test]
public void TestKeyboardNavigationAfterOrderChange()
{
AddStep("add rooms", () => RoomManager.AddRooms(3));
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(3)));
AddStep("reorder rooms", () =>
{
var room = RoomManager.Rooms[1];
var room = rooms[1];
rooms.Remove(room);
RoomManager.RemoveRoom(room);
RoomManager.AddOrUpdateRoom(room);
room.RoomID += 3;
rooms.Add(room);
});
AddAssert("no selection", () => checkRoomSelected(null));
@@ -116,12 +122,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestClickDeselection()
{
AddStep("add room", () => RoomManager.AddRooms(1));
AddStep("add room", () => rooms.AddRange(GenerateRooms(1)));
AddAssert("no selection", () => checkRoomSelected(null));
press(Key.Down);
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
AddAssert("first room selected", () => checkRoomSelected(container.DrawableRooms.First().Room));
AddStep("click away", () => InputManager.Click(MouseButton.Left));
AddAssert("no selection", () => checkRoomSelected(null));
@@ -135,34 +141,34 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestStringFiltering()
{
AddStep("add rooms", () => RoomManager.AddRooms(4));
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(4)));
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
AddUntilStep("4 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 4);
AddStep("filter one room", () => container.Filter.Value = new FilterCriteria { SearchString = "1" });
AddStep("filter one room", () => container.Filter.Value = new FilterCriteria { SearchString = rooms.First().Name });
AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1);
AddUntilStep("1 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 1);
AddStep("remove filter", () => container.Filter.Value = null);
AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4);
AddUntilStep("4 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 4);
}
[Test]
public void TestRulesetFiltering()
{
AddStep("add rooms", () => RoomManager.AddRooms(2, new OsuRuleset().RulesetInfo));
AddStep("add rooms", () => RoomManager.AddRooms(3, new CatchRuleset().RulesetInfo));
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(2, new OsuRuleset().RulesetInfo)));
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(3, new CatchRuleset().RulesetInfo)));
// Todo: What even is this case...?
AddStep("set empty filter criteria", () => container.Filter.Value = new FilterCriteria());
AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5);
AddUntilStep("5 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 5);
AddStep("filter osu! rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo });
AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddUntilStep("2 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 2);
AddStep("filter catch rooms", () => container.Filter.Value = new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo });
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
AddUntilStep("3 rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 3);
}
[Test]
@@ -170,30 +176,46 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add rooms", () =>
{
RoomManager.AddRooms(1, withPassword: true);
RoomManager.AddRooms(1, withPassword: false);
rooms.AddRange(GenerateRooms(1, withPassword: true));
rooms.AddRange(GenerateRooms(1, withPassword: false));
});
AddStep("apply default filter", () => container.Filter.SetDefault());
AddUntilStep("both rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2);
AddUntilStep("both rooms visible", () => container.DrawableRooms.Count(r => r.IsPresent) == 2);
AddStep("filter public rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Public });
AddUntilStep("private room hidden", () => container.Rooms.All(r => !r.Room.HasPassword));
AddUntilStep("private room hidden", () => container.DrawableRooms.All(r => !r.Room.HasPassword));
AddStep("filter private rooms", () => container.Filter.Value = new FilterCriteria { Permissions = RoomPermissionsFilter.Private });
AddUntilStep("public room hidden", () => container.Rooms.All(r => r.Room.HasPassword));
AddUntilStep("public room hidden", () => container.DrawableRooms.All(r => r.Room.HasPassword));
}
[Test]
public void TestPasswordProtectedRooms()
{
AddStep("add rooms", () => RoomManager.AddRooms(3, withPassword: true));
AddStep("add rooms", () => rooms.AddRange(GenerateRooms(3, withPassword: true)));
}
private bool checkRoomSelected(Room? room) => SelectedRoom.Value == room;
[Test]
public void TestFreestyleBypassesRulesetFilter()
{
AddStep("apply taiko filter", () => container.Filter.Value = new FilterCriteria { Ruleset = new TaikoRuleset().RulesetInfo });
AddStep("add osu + freestyle room", () =>
{
var room = GenerateRooms(1, new OsuRuleset().RulesetInfo)[0];
room.Playlist[0].Freestyle = true;
room.CurrentPlaylistItem = room.Playlist[0];
rooms.Add(room);
});
AddAssert("room visible", () => container.DrawableRooms.Any());
}
private bool checkRoomSelected(Room? room) => selectedRoom.Value == room;
private Room? getRoomInFlow(int index) =>
(container.ChildrenOfType<FillFlowContainer<DrawableLoungeRoom>>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
@@ -72,10 +72,10 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
AddStep("User began playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
@@ -88,13 +88,12 @@ namespace osu.Game.Tests.Visual.Online
{
IDisposable token = null!;
AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0));
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == streamingUser.Id);
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id));
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
AddStep("End watching user presence", () => token.Dispose());
@@ -5,46 +5,227 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Metadata;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard.Friends;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneFriendDisplay : OsuTestScene
{
protected override bool UseOnlineAPI => true;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
private FriendDisplay display;
private TestMetadataClient metadataClient;
[SetUp]
public void Setup() => Schedule(() =>
{
Child = new BasicScrollContainer
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
Child = display = new FriendDisplay()
CachedDependencies =
[
(typeof(MetadataClient), metadataClient = new TestMetadataClient())
],
Children = new Drawable[]
{
metadataClient,
new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FriendDisplay()
}
}
};
});
[Test]
public void TestOffline()
public void TestAddAndRemoveFriends()
{
AddStep("Populate with offline test users", () => display.Users = getUsers());
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
TargetUser = u
}));
});
waitForLoad();
assertVisiblePanelCount<UserPanel>(3);
AddStep("remove one friend", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.RemoveAt(0);
});
waitForLoad();
assertVisiblePanelCount<UserPanel>(2);
AddStep("add one friend", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.AddRange(getUsers().Take(1).Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
TargetUser = u
}));
});
waitForLoad();
assertVisiblePanelCount<UserPanel>(3);
}
[Test]
public void TestOnline()
public void TestChangeDisplayStyle()
{
// No need to do anything, fetch is performed automatically.
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
TargetUser = u
}));
});
waitForLoad();
assertVisiblePanelCount<UserGridPanel>(3);
AddStep("set list style", () => this.ChildrenOfType<UserListToolbar>().Single().DisplayStyle.Value = OverlayPanelDisplayStyle.List);
waitForLoad();
assertVisiblePanelCount<UserListPanel>(3);
AddStep("set brick style", () => this.ChildrenOfType<UserListToolbar>().Single().DisplayStyle.Value = OverlayPanelDisplayStyle.Brick);
waitForLoad();
assertVisiblePanelCount<UserBrickPanel>(3);
}
[Test]
public void TestOnlinePresence()
{
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
TargetUser = u
}));
});
waitForLoad();
assertVisiblePanelCount<UserPanel>(3);
AddStep("change to online stream", () => this.ChildrenOfType<FriendOnlineStreamControl>().Single().Current.Value = OnlineStatus.Online);
assertVisiblePanelCount<UserPanel>(0);
AddStep("bring a friend online", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
metadataClient.FriendPresenceUpdated(api.Friends[0].TargetID, new UserPresence { Status = UserStatus.Online });
});
assertVisiblePanelCount<UserPanel>(1);
AddStep("change to offline stream", () => this.ChildrenOfType<FriendOnlineStreamControl>().Single().Current.Value = OnlineStatus.Offline);
assertVisiblePanelCount<UserPanel>(2);
AddStep("bring a friend online", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
metadataClient.FriendPresenceUpdated(api.Friends[1].TargetID, new UserPresence { Status = UserStatus.Online });
});
assertVisiblePanelCount<UserPanel>(1);
AddStep("change to online stream", () => this.ChildrenOfType<FriendOnlineStreamControl>().Single().Current.Value = OnlineStatus.Online);
assertVisiblePanelCount<UserPanel>(2);
AddStep("take friend offline", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
metadataClient.FriendPresenceUpdated(api.Friends[1].TargetID, null);
});
assertVisiblePanelCount<UserPanel>(1);
AddStep("change to all stream", () => this.ChildrenOfType<FriendOnlineStreamControl>().Single().Current.Value = OnlineStatus.All);
assertVisiblePanelCount<UserPanel>(3);
}
[Test]
public void TestLoadFriendsBeforeDisplay()
{
AddStep("set friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(getUsers().Select(u => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = u.OnlineID,
TargetUser = u
}));
});
AddStep("load new display", () =>
{
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies =
[
(typeof(MetadataClient), metadataClient = new TestMetadataClient())
],
Children = new Drawable[]
{
metadataClient,
new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FriendDisplay()
}
}
};
});
waitForLoad();
assertVisiblePanelCount<UserPanel>(3);
}
private void waitForLoad()
=> AddUntilStep("wait for panels to load", () => this.ChildrenOfType<LoadingSpinner>().Single().State.Value, () => Is.EqualTo(Visibility.Hidden));
private void assertVisiblePanelCount<T>(int expectedVisible)
where T : UserPanel
{
AddAssert($"{typeof(T).ReadableName()}s in list", () => this.ChildrenOfType<FriendsList>().Last().ChildrenOfType<UserPanel>().All(p => p is T));
AddAssert($"{expectedVisible} panels visible", () => this.ChildrenOfType<FriendsList>().Last().ChildrenOfType<FriendsList.FilterableUserPanel>().Count(p => p.IsPresent),
() => Is.EqualTo(expectedVisible));
}
private List<APIUser> getUsers() => new List<APIUser>
@@ -3,7 +3,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.Containers;
@@ -17,25 +16,22 @@ namespace osu.Game.Tests.Visual.Playlists
{
public partial class TestScenePlaylistsLoungeSubScreen : OnlinePlayTestScene
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private TestLoungeSubScreen loungeScreen = null!;
private PlaylistsLoungeSubScreen loungeScreen = null!;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("push screen", () => LoadScreen(loungeScreen = new TestLoungeSubScreen()));
AddStep("push screen", () => LoadScreen(loungeScreen = new PlaylistsLoungeSubScreen()));
AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen());
}
private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType<RoomsContainer>().First();
private RoomListing roomListing => loungeScreen.ChildrenOfType<RoomListing>().First();
[Test]
public void TestManyRooms()
{
AddStep("add rooms", () => RoomManager.AddRooms(500));
createRooms(GenerateRooms(500));
}
[Test]
@@ -43,59 +39,41 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("reset mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
createRooms(GenerateRooms(30));
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
AddStep("move mouse to third room", () => InputManager.MoveMouseTo(roomsContainer.Rooms[2]));
AddStep("move mouse to third room", () => InputManager.MoveMouseTo(roomListing.DrawableRooms[2]));
AddStep("hold down", () => InputManager.PressButton(MouseButton.Left));
AddStep("drag to top", () => InputManager.MoveMouseTo(roomsContainer.Rooms[0]));
AddStep("drag to top", () => InputManager.MoveMouseTo(roomListing.DrawableRooms[0]));
AddAssert("first and second room masked", ()
=> !checkRoomVisible(roomsContainer.Rooms[0]) &&
!checkRoomVisible(roomsContainer.Rooms[1]));
=> !checkRoomVisible(roomListing.DrawableRooms[0]) &&
!checkRoomVisible(roomListing.DrawableRooms[1]));
}
[Test]
public void TestScrollSelectedIntoView()
{
AddStep("add rooms", () => RoomManager.AddRooms(30));
AddUntilStep("wait for rooms", () => roomsContainer.Rooms.Count == 30);
createRooms(GenerateRooms(30));
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms[0]));
AddStep("select last room", () => roomListing.DrawableRooms[^1].TriggerClick());
AddStep("select last room", () => roomsContainer.Rooms[^1].TriggerClick());
AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms[0]));
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", () => !loungeScreen.SelectedRoom.Disabled);
AddStep("select room", () => roomsContainer.Rooms[0].TriggerClick());
AddAssert("selected room is non-null", () => loungeScreen.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", () => loungeScreen.SelectedRoom.Value != null);
AddAssert("selected room is disabled", () => loungeScreen.SelectedRoom.Disabled);
AddUntilStep("first room is masked", () => !checkRoomVisible(roomListing.DrawableRooms[0]));
AddUntilStep("last room is not masked", () => checkRoomVisible(roomListing.DrawableRooms[^1]));
}
private bool checkRoomVisible(DrawableRoom room) =>
loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad
.Contains(room.ScreenSpaceDrawQuad.Centre);
private partial class TestLoungeSubScreen : PlaylistsLoungeSubScreen
private void createRooms(params Room[] rooms)
{
public new Bindable<Room?> SelectedRoom => base.SelectedRoom;
AddStep("create rooms", () =>
{
foreach (var room in rooms)
API.Queue(new CreateRoomRequest(room));
});
AddStep("refresh lounge", () => loungeScreen.RefreshRooms());
}
}
}
@@ -3,14 +3,13 @@
using System;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Visual.OnlinePlay;
@@ -18,21 +17,38 @@ namespace osu.Game.Tests.Visual.Playlists
{
public partial class TestScenePlaylistsMatchSettingsOverlay : OnlinePlayTestScene
{
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
private TestRoomSettings settings = null!;
protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies();
private Room room = null!;
private Func<Room, string?>? handleRequest;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("setup api", () =>
{
handleRequest = null;
((DummyAPIAccess)API).HandleRequest = req =>
{
if (req is not CreateRoomRequest createReq || handleRequest == null)
return false;
if (handleRequest(createReq.Room) is string errorText)
createReq.TriggerFailure(new APIException(errorText, null));
else
{
var createdRoom = new APICreatedRoom();
createdRoom.CopyFrom(createReq.Room);
createReq.TriggerSuccess(createdRoom);
}
return true;
};
});
AddStep("create overlay", () =>
{
SelectedRoom.Value = new Room();
Child = settings = new TestRoomSettings(SelectedRoom.Value!)
Child = settings = new TestRoomSettings(room = new Room())
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible }
@@ -45,19 +61,19 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("clear name and beatmap", () =>
{
SelectedRoom.Value!.Name = "";
SelectedRoom.Value!.Playlist = [];
room.Name = "";
room.Playlist = [];
});
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
AddStep("set name", () => SelectedRoom.Value!.Name = "Room name");
AddStep("set name", () => room.Name = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
AddStep("set beatmap", () => SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]);
AddStep("set beatmap", () => room.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)]);
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
AddStep("clear name", () => SelectedRoom.Value!.Name = "");
AddStep("clear name", () => room.Name = "");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
}
@@ -73,12 +89,12 @@ namespace osu.Game.Tests.Visual.Playlists
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
room.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = r =>
handleRequest = r =>
{
createdRoom = r;
return string.Empty;
return null;
};
});
@@ -98,22 +114,22 @@ namespace osu.Game.Tests.Visual.Playlists
{
var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo;
SelectedRoom.Value!.Name = "Test Room";
SelectedRoom.Value!.Playlist = [new PlaylistItem(beatmap)];
room.Name = "Test Room";
room.Playlist = [new PlaylistItem(beatmap)];
errorMessage = $"{not_found_prefix} {beatmap.OnlineID}";
RoomManager.CreateRequested = _ => errorMessage;
handleRequest = _ => errorMessage;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
AddAssert("playlist item valid", () => SelectedRoom.Value!.Playlist[0].Valid.Value);
AddAssert("playlist item valid", () => room.Playlist[0].Valid.Value);
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
AddAssert("error displayed", () => settings.ErrorText.IsPresent);
AddAssert("error has custom text", () => settings.ErrorText.Text != errorMessage);
AddAssert("playlist item marked invalid", () => !SelectedRoom.Value!.Playlist[0].Valid.Value);
AddAssert("playlist item marked invalid", () => !room.Playlist[0].Valid.Value);
}
[Test]
@@ -125,10 +141,10 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("setup", () =>
{
SelectedRoom.Value!.Name = "Test Room";
SelectedRoom.Value!.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
room.Name = "Test Room";
room.Playlist = [new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)];
RoomManager.CreateRequested = _ => failText;
handleRequest = _ => failText;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
@@ -159,48 +175,5 @@ namespace osu.Game.Tests.Visual.Playlists
{
}
}
private class TestDependencies : OnlinePlayTestSceneDependencies
{
protected override IRoomManager CreateRoomManager() => new TestRoomManager();
}
protected class TestRoomManager : IRoomManager
{
public Func<Room, string>? CreateRequested;
public event Action RoomsUpdated
{
add { }
remove { }
}
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
public IBindableList<Room> Rooms => null!;
public void AddOrUpdateRoom(Room room) => throw new NotImplementedException();
public void RemoveRoom(Room room) => throw new NotImplementedException();
public void ClearRooms() => throw new NotImplementedException();
public void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null)
{
if (CreateRequested == null)
return;
string error = CreateRequested.Invoke(room);
if (!string.IsNullOrEmpty(error))
onError?.Invoke(error);
else
onSuccess?.Invoke(room);
}
public void JoinRoom(Room room, string? password, Action<Room>? onSuccess = null, Action<string>? onError = null) => throw new NotImplementedException();
public void PartRoom() => throw new NotImplementedException();
}
}
}
@@ -14,13 +14,15 @@ namespace osu.Game.Tests.Visual.Playlists
{
public partial class TestScenePlaylistsParticipantsList : OnlinePlayTestScene
{
private Room room = null!;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("create list", () =>
AddStep("create room", () =>
{
SelectedRoom.Value = new Room
room = new Room
{
RoomID = 7,
RecentParticipants = Enumerable.Range(0, 50).Select(_ => new APIUser
@@ -38,7 +40,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("create component", () =>
{
Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Horizontal)
Child = new ParticipantsDisplay(room, Direction.Horizontal)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -52,7 +54,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("create component", () =>
{
Child = new ParticipantsDisplay(SelectedRoom.Value!, Direction.Vertical)
Child = new ParticipantsDisplay(room, Direction.Vertical)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -7,9 +7,12 @@ using System.Linq;
using System.Net;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
@@ -32,6 +35,9 @@ namespace osu.Game.Tests.Visual.Playlists
private const int scores_per_result = 10;
private const int real_user_position = 200;
[Cached]
private readonly BeatmapLookupCache beatmapLookupCache = new BeatmapLookupCache();
private ResultsScreen resultsScreen = null!;
private int lowestScoreId; // Score ID of the lowest score in the list.
@@ -41,6 +47,11 @@ namespace osu.Game.Tests.Visual.Playlists
private int totalCount;
private ScoreInfo userScore = null!;
public TestScenePlaylistsResultsScreen()
{
Add(beatmapLookupCache);
}
[SetUpSteps]
public override void SetUpSteps()
{
@@ -58,9 +69,11 @@ namespace osu.Game.Tests.Visual.Playlists
totalCount = 0;
userScore = TestResources.CreateTestScoreInfo();
userScore.OnlineID = 1;
userScore.TotalScore = 0;
userScore.Statistics = new Dictionary<HitResult, int>();
userScore.MaximumStatistics = new Dictionary<HitResult, int>();
userScore.Position = real_user_position;
// Beatmap is required to be an actual beatmap so the scores can get their scores correctly
// calculated for standardised scoring, else the tests that rely on ordering will fall over.
@@ -143,13 +156,13 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
AddAssert("right loading spinner shown", () =>
AddUntilStep("right loading spinner shown", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
waitForDisplay();
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
AddAssert("right loading spinner hidden", () =>
AddUntilStep("right loading spinner hidden", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
}
}
@@ -167,26 +180,26 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
AddAssert("right loading spinner shown", () =>
AddUntilStep("right loading spinner shown", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
waitForDisplay();
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
AddAssert("right loading spinner hidden", () =>
AddUntilStep("right loading spinner hidden", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
AddStep("bind delayed handler with no scores", () => bindHandler(delayed: true, noScores: true));
AddStep("scroll to right", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToEnd(false));
AddAssert("right loading spinner shown", () =>
AddUntilStep("right loading spinner shown", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible);
waitForDisplay();
AddAssert("count not increased", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount);
AddAssert("right loading spinner hidden", () =>
AddUntilStep("right loading spinner hidden", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden);
AddAssert("no placeholders shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.Zero);
@@ -209,13 +222,13 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
AddStep("scroll to left", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToStart(false));
AddAssert("left loading spinner shown", () =>
AddUntilStep("left loading spinner shown", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Visible);
waitForDisplay();
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
AddAssert("left loading spinner hidden", () =>
AddUntilStep("left loading spinner hidden", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden);
}
}
@@ -229,7 +242,36 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("bind user score info handler", () => bindHandler(noScores: true));
createUserBestResults();
AddAssert("no scores visible", () => !resultsScreen.ChildrenOfType<ScorePanelList>().Single().GetScorePanels().Any());
AddAssert("placeholder shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.EqualTo(1));
AddUntilStep("placeholder shown", () => this.ChildrenOfType<MessagePlaceholder>().Count(), () => Is.EqualTo(1));
}
[Test]
public void TestFetchingAllTheWayToFirstNeverDisplaysNegativePosition()
{
AddStep("set user position", () => userScore.Position = 20);
AddStep("bind user score info handler", () => bindHandler(userScore: userScore));
createResultsWithScore(() => userScore);
waitForDisplay();
AddStep("bind delayed handler", () => bindHandler(true));
for (int i = 0; i < 2; i++)
{
AddStep("simulate user falling down ranking", () => userScore.Position += 2);
AddStep("scroll to left", () => resultsScreen.ChildrenOfType<ScorePanelList>().Single().ChildrenOfType<OsuScrollContainer>().Single().ScrollToStart(false));
AddUntilStep("left loading spinner shown", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Visible);
waitForDisplay();
AddUntilStep("left loading spinner hidden", () =>
resultsScreen.ChildrenOfType<LoadingSpinner>().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden);
}
AddAssert("total count is 34", () => this.ChildrenOfType<ScorePanel>().Count(), () => Is.EqualTo(34));
AddUntilStep("all panels have non-negative position", () => this.ChildrenOfType<ScorePanel>().All(p => p.ScorePosition.Value > 0));
}
private void createResultsWithScore(Func<ScoreInfo> getScore)
@@ -279,6 +321,25 @@ namespace osu.Game.Tests.Visual.Playlists
case IndexPlaylistScoresRequest:
break;
case GetBeatmapsRequest getBeatmaps:
getBeatmaps.TriggerSuccess(new GetBeatmapsResponse
{
Beatmaps = getBeatmaps.BeatmapIds.Select(id => new APIBeatmap
{
OnlineID = id,
StarRating = id,
DifficultyName = $"Beatmap {id}",
BeatmapSet = new APIBeatmapSet
{
Title = $"Title {id}",
Artist = $"Artist {id}",
AuthorString = $"Author {id}"
}
}).ToList()
});
return true;
default:
return false;
}
@@ -301,7 +362,7 @@ namespace osu.Game.Tests.Visual.Playlists
if (userScore == null)
triggerFail(s);
else
triggerSuccess(s, createUserResponse(userScore));
triggerSuccess(s, () => createUserResponse(userScore));
break;
@@ -309,12 +370,12 @@ namespace osu.Game.Tests.Visual.Playlists
if (userScore == null)
triggerFail(u);
else
triggerSuccess(u, createUserResponse(userScore));
triggerSuccess(u, () => createUserResponse(userScore));
break;
case IndexPlaylistScoresRequest i:
triggerSuccess(i, createIndexResponse(i, noScores));
triggerSuccess(i, () => createIndexResponse(i, noScores));
break;
}
}, delay);
@@ -322,11 +383,11 @@ namespace osu.Game.Tests.Visual.Playlists
return true;
};
private void triggerSuccess<T>(APIRequest<T> req, T result)
private void triggerSuccess<T>(APIRequest<T> req, Func<T> result)
where T : class
{
requestComplete = true;
req.TriggerSuccess(result);
req.TriggerSuccess(result.Invoke());
}
private void triggerFail(APIRequest req)
@@ -337,33 +398,20 @@ namespace osu.Game.Tests.Visual.Playlists
private MultiplayerScore createUserResponse(ScoreInfo userScore)
{
var multiplayerUserScore = new MultiplayerScore
{
ID = highestScoreId,
Accuracy = userScore.Accuracy,
Passed = userScore.Passed,
Rank = userScore.Rank,
Position = real_user_position,
MaxCombo = userScore.MaxCombo,
User = userScore.User,
ScoresAround = new MultiplayerScoresAround
{
Higher = new MultiplayerScores(),
Lower = new MultiplayerScores()
}
};
var multiplayerUserScore = createMultiplayerUserScore(userScore);
totalCount++;
for (int i = 1; i <= scores_per_result; i++)
{
multiplayerUserScore.ScoresAround.Lower.Scores.Add(new MultiplayerScore
multiplayerUserScore.ScoresAround!.Lower!.Scores.Add(new MultiplayerScore
{
ID = getNextLowestScoreId(),
Accuracy = userScore.Accuracy,
Passed = true,
Rank = userScore.Rank,
MaxCombo = userScore.MaxCombo,
BeatmapId = RNG.Next(0, 7),
User = new APIUser
{
Id = 2 + i,
@@ -372,13 +420,14 @@ namespace osu.Game.Tests.Visual.Playlists
},
});
multiplayerUserScore.ScoresAround.Higher.Scores.Add(new MultiplayerScore
multiplayerUserScore.ScoresAround!.Higher!.Scores.Add(new MultiplayerScore
{
ID = getNextHighestScoreId(),
Accuracy = userScore.Accuracy,
Passed = true,
Rank = userScore.Rank,
MaxCombo = userScore.MaxCombo,
BeatmapId = RNG.Next(0, 7),
User = new APIUser
{
Id = 2 + i,
@@ -390,13 +439,33 @@ namespace osu.Game.Tests.Visual.Playlists
totalCount += 2;
}
addCursor(multiplayerUserScore.ScoresAround.Lower);
addCursor(multiplayerUserScore.ScoresAround.Higher);
addCursor(multiplayerUserScore.ScoresAround!.Lower!);
addCursor(multiplayerUserScore.ScoresAround!.Higher!);
return multiplayerUserScore;
}
private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req, bool noScores = false)
private MultiplayerScore createMultiplayerUserScore(ScoreInfo userScore)
{
return new MultiplayerScore
{
ID = highestScoreId,
Accuracy = userScore.Accuracy,
Passed = userScore.Passed,
Rank = userScore.Rank,
Position = userScore.Position,
MaxCombo = userScore.MaxCombo,
User = userScore.User,
BeatmapId = RNG.Next(0, 7),
ScoresAround = new MultiplayerScoresAround
{
Higher = new MultiplayerScores(),
Lower = new MultiplayerScores()
}
};
}
private IndexedMultiplayerScores createIndexResponse(IndexPlaylistScoresRequest req, bool noScores)
{
var result = new IndexedMultiplayerScores();
@@ -404,15 +473,26 @@ namespace osu.Game.Tests.Visual.Playlists
string sort = req.IndexParams?.Properties["sort"].ToObject<string>() ?? "score_desc";
bool reachedEnd = false;
for (int i = 1; i <= scores_per_result; i++)
{
int nextId = sort == "score_asc" ? getNextHighestScoreId() : getNextLowestScoreId();
if (userScore.OnlineID - nextId >= userScore.Position)
{
reachedEnd = true;
break;
}
result.Scores.Add(new MultiplayerScore
{
ID = sort == "score_asc" ? getNextHighestScoreId() : getNextLowestScoreId(),
ID = nextId,
Accuracy = 1,
Passed = true,
Rank = ScoreRank.X,
MaxCombo = 1000,
BeatmapId = RNG.Next(0, 7),
User = new APIUser
{
Id = 2 + i,
@@ -424,7 +504,10 @@ namespace osu.Game.Tests.Visual.Playlists
totalCount++;
}
addCursor(result);
if (!reachedEnd)
addCursor(result);
result.UserScore = createMultiplayerUserScore(userScore);
return result;
}
@@ -35,6 +35,7 @@ namespace osu.Game.Tests.Visual.Playlists
private BeatmapManager manager = null!;
private TestPlaylistsRoomSubScreen match = null!;
private BeatmapSetInfo importedBeatmap = null!;
private Room room = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
@@ -47,11 +48,9 @@ namespace osu.Game.Tests.Visual.Playlists
[SetUpSteps]
public void SetupSteps()
{
AddStep("set room", () => SelectedRoom.Value = new Room());
importBeatmap();
AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(SelectedRoom.Value!)));
AddStep("load match", () => LoadScreen(match = new TestPlaylistsRoomSubScreen(room = new Room())));
AddUntilStep("wait for load", () => match.IsCurrentScreen());
}
@@ -119,7 +118,7 @@ namespace osu.Game.Tests.Visual.Playlists
];
});
AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value!.Playlist[0]);
AddAssert("first playlist item selected", () => match.SelectedItem.Value == room.Playlist[0]);
}
[Test]
@@ -197,10 +196,9 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash);
}
private void setupAndCreateRoom(Action<Room> room)
private void setupAndCreateRoom(Action<Room> setupFunc)
{
AddStep("setup room", () => room(SelectedRoom.Value!));
AddStep("setup room", () => setupFunc(room));
AddStep("click create button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<PlaylistsRoomSettingsOverlay.CreateRoomButton>().Single());
@@ -0,0 +1,616 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.Playlists
{
public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
{
private BeatmapManager beatmaps = null!;
private BeatmapSetInfo importedSet = null!;
[BackgroundDependencyLoader]
private void load(GameHost host, AudioManager audio)
{
BeatmapStore beatmapStore;
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default));
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(Realm);
Add(beatmapStore);
importedSet = beatmaps.Import(new BeatmapSetInfo
{
OnlineID = TestResources.GetNextTestID(),
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
DateAdded = DateTimeOffset.UtcNow,
Beatmaps =
{
new BeatmapInfo
{
OnlineID = 1,
DifficultyName = "Osu 1",
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = new OsuRuleset().RulesetInfo,
Metadata =
{
Artist = "Some Artist",
Title = "Some Song",
Author = { Username = "Some Guy" },
},
},
new BeatmapInfo
{
OnlineID = 2,
DifficultyName = "Osu 2",
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = new OsuRuleset().RulesetInfo,
Metadata =
{
Artist = "Some Artist",
Title = "Some Song",
Author = { Username = "Some Guy" },
},
},
new BeatmapInfo
{
OnlineID = 3,
DifficultyName = "Taiko 1",
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = new TaikoRuleset().RulesetInfo,
Metadata =
{
Artist = "Some Artist",
Title = "Some Song",
Author = { Username = "Some Guy" },
},
},
new BeatmapInfo
{
OnlineID = 4,
DifficultyName = "Taiko 2",
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Ruleset = new TaikoRuleset().RulesetInfo,
Metadata =
{
Artist = "Some Artist",
Title = "Some Song",
Author = { Username = "Some Guy" },
},
}
}
})!.PerformRead(s => s.Detach());
}
/// <summary>
/// Tests that the beatmap and ruleset are adjusted to follow the selected item.
/// </summary>
[Test]
public void TestBeatmapAndRuleset_FollowSelection()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
// osu! beatmap
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
// osu! beatmap converted played in taiko
new PlaylistItem(importedSet.Beatmaps[1])
{
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
Freestyle = true
}
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("select first item", () => screen.SelectedItem.Value = room.Playlist[0]);
AddUntilStep("first beatmap selected", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[0]));
AddUntilStep("osu ruleset selected", () => Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("second beatmap selected", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[1]));
AddUntilStep("taiko ruleset selected", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
}
/// <summary>
/// Tests that the beatmap style is reset when the selected item is changed.
/// </summary>
[Test]
public void TestBeatmapStyle_Reset_OnSelection()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user beatmap style", () => screen.UserBeatmap.Value = importedSet.Beatmaps[1]);
AddUntilStep("user beatmap selected", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[1]));
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user beatmap style reset", () => screen.UserBeatmap.Value == null);
AddUntilStep("second beatmap selected", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[0]));
}
/// <summary>
/// Tests that the ruleset style is reset when the selected item is changed and it's no longer valid.
/// </summary>
[Test]
public void TestRulesetStyle_Reset_OnSelection_IfNotValid()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user ruleset style", () => screen.UserRuleset.Value = new ManiaRuleset().RulesetInfo);
AddUntilStep("user ruleset selected", () => Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo));
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user ruleset style reset", () => screen.UserRuleset.Value == null);
AddUntilStep("second ruleset selected", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
}
/// <summary>
/// Tests that the ruleset style is preserved when the selected item is changed and the ruleset is still valid.
/// </summary>
[Test]
public void TestRulesetStyle_Preserved_OnSelection_IfStillValid()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user ruleset style", () => screen.UserRuleset.Value = new ManiaRuleset().RulesetInfo);
AddUntilStep("user ruleset selected", () => Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo));
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user ruleset style preserved", () => screen.UserRuleset.Value!.Equals(new ManiaRuleset().RulesetInfo));
AddUntilStep("user ruleset selected", () => Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo));
}
/// <summary>
/// Tests that mod style is reset when the selected item is changed to another with an inconvertible ruleset.
/// No user style is assumed.
/// </summary>
[Test]
public void TestModsReset_OnSelection_DifferentRuleset_NoUserStyle()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user mods", () => screen.UserMods.Value = [new OsuModDoubleTime()]);
AddUntilStep("user mods selected", () => SelectedMods.Value.OfType<OsuModDoubleTime>().Any());
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user mod style reset", () => !screen.UserMods.Value.Any());
AddUntilStep("mods reset", () => !SelectedMods.Value.Any());
}
/// <summary>
/// Tests that mod style is preserved when the selected item is changed to another with the same ruleset.
/// No user style is assumed.
/// </summary>
[Test]
public void TestModsPreserved_OnSelection_SameRuleset_NoUserStyle()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user mods", () => screen.UserMods.Value = [new OsuModDoubleTime()]);
AddUntilStep("user mods selected", () => SelectedMods.Value.OfType<OsuModDoubleTime>().Any());
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user mod style preserved", () => screen.UserMods.Value.OfType<OsuModDoubleTime>().Any());
AddUntilStep("mods preserved", () => SelectedMods.Value.OfType<OsuModDoubleTime>().Any());
}
/// <summary>
/// Tests that mod style is reset when the selected item is changed to another with an inconvertible ruleset.
/// A user beatmap/ruleset style is assumed.
/// </summary>
[Test]
public void TestModsReset_OnSelection_DifferentRuleset_WithUserStyle()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user ruleset", () => screen.UserRuleset.Value = new CatchRuleset().RulesetInfo);
AddUntilStep("user ruleset selected", () => Ruleset.Value.Equals(new CatchRuleset().RulesetInfo));
AddStep("set user mods", () => screen.UserMods.Value = [new CatchModDoubleTime()]);
AddUntilStep("user mods selected", () => SelectedMods.Value.OfType<CatchModDoubleTime>().Any());
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user mod style reset", () => !screen.UserMods.Value.Any());
AddUntilStep("mods reset", () => !SelectedMods.Value.Any());
}
/// <summary>
/// Tests that mod style is preserved when the selected item is changed to another with the same ruleset.
/// A user beatmap/ruleset style is assumed.
/// </summary>
[Test]
public void TestModsPreserved_OnSelection_SameRuleset_WithStyle()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user ruleset", () => screen.UserRuleset.Value = new TaikoRuleset().RulesetInfo);
AddUntilStep("user ruleset selected", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
AddStep("set user mods", () => screen.UserMods.Value = [new TaikoModDoubleTime()]);
AddUntilStep("user mods selected", () => SelectedMods.Value.OfType<TaikoModDoubleTime>().Any());
AddStep("select second item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user mod style preserved", () => screen.UserMods.Value.OfType<TaikoModDoubleTime>().Any());
AddUntilStep("mods preserved", () => SelectedMods.Value.OfType<TaikoModDoubleTime>().Any());
}
/// <summary>
/// Tests that the mod style is revalidated when the ruleset style is changed.
/// </summary>
[Test]
public void TestModsValidated_OnRulesetStyleChanged()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
AddStep("set user mods", () => screen.UserMods.Value = [new OsuModDoubleTime()]);
AddUntilStep("user mods selected", () => SelectedMods.Value.OfType<OsuModDoubleTime>().Any());
AddStep("set user ruleset", () => screen.UserRuleset.Value = new TaikoRuleset().RulesetInfo);
AddUntilStep("user ruleset selected", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
AddUntilStep("user mods reset", () => !screen.UserMods.Value.Any());
AddUntilStep("mods reset", () => !SelectedMods.Value.Any());
}
/// <summary>
/// Tests that the beatmap and ruleset style are reset when the selected item is changed to one without freestyle,
/// and that the mod selection is re-validated against the item's allowed mods.
/// </summary>
[Test]
public void TestUserStyle_Reset_OnFreestyleDisabled()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = [new APIMod(new OsuModDoubleTime())]
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsRoomSubScreen screen = null!;
AddStep("load screen", () => LoadScreen(new TestPlaylistsScreen(screen = new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for load", () => screen.IsLoaded);
// Set beatmap + ruleset, reset by selecting second playlist item
AddStep("set user beatmap/ruleset style", () =>
{
screen.UserBeatmap.Value = importedSet.Beatmaps[1];
screen.UserRuleset.Value = new TaikoRuleset().RulesetInfo;
});
AddUntilStep("beatmap/ruleset set", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[1]) && Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
AddStep("select second playlist item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user style reset", () => screen.UserBeatmap.Value == null && screen.UserRuleset.Value == null);
AddUntilStep("beatmap/ruleset set", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[0]) && Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
AddStep("select first playlist item", () => screen.SelectedItem.Value = room.Playlist[0]);
// Set mods (DT+HR), validate by selecting second playlist item where only DT is allowed.
AddStep("set user mods style", () => screen.UserMods.Value = [new OsuModDoubleTime(), new OsuModHardRock()]);
AddUntilStep("mods set", () => SelectedMods.Value.OfType<OsuModDoubleTime>().Any() && SelectedMods.Value.OfType<OsuModHardRock>().Any());
AddStep("select second playlist item", () => screen.SelectedItem.Value = room.Playlist[1]);
AddUntilStep("user mods validated", () => screen.UserMods.Value.Count == 1 && screen.UserMods.Value.OfType<OsuModDoubleTime>().Any());
AddUntilStep("mods set", () => SelectedMods.Value.Count == 1 && SelectedMods.Value.OfType<OsuModDoubleTime>().Any());
}
private partial class TestPlaylistsScreen : OsuScreen
{
public TestPlaylistsScreen(PlaylistsRoomSubScreen screen)
{
OnlinePlaySubScreenStack stack;
InternalChildren = new Drawable[]
{
stack = new OnlinePlaySubScreenStack
{
RelativeSizeAxes = Axes.Both
},
new BackButton
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
State = { Value = Visibility.Visible },
Action = () =>
{
if (stack.CurrentScreen is not PlaylistsRoomSubScreen)
stack.Exit();
}
}
};
stack.Push(screen);
}
}
private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
{
public new Bindable<PlaylistItem?> SelectedItem => base.SelectedItem;
public new Bindable<BeatmapInfo?> UserBeatmap => base.UserBeatmap;
public new Bindable<RulesetInfo?> UserRuleset => base.UserRuleset;
public new Bindable<IReadOnlyList<Mod>> UserMods => base.UserMods;
public TestPlaylistsRoomSubScreen(Room room)
: base(room)
{
}
}
}
}
@@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Ranking
showPanel(TestResources.CreateTestScoreInfo(beatmap));
});
AddAssert("pp display faded out", () =>
AddUntilStep("pp display faded out", () =>
{
var ppDisplay = this.ChildrenOfType<PerformanceStatistic>().Single();
return ppDisplay.Alpha == 0.5 && ppDisplay.TooltipText == ResultsScreenStrings.NoPPForUnrankedBeatmaps;
@@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Ranking
showPanel(score);
});
AddAssert("pp display faded out", () =>
AddUntilStep("pp display faded out", () =>
{
var ppDisplay = this.ChildrenOfType<PerformanceStatistic>().Single();
return ppDisplay.Alpha == 0.5 && ppDisplay.TooltipText == ResultsScreenStrings.NoPPForUnrankedMods;
@@ -116,7 +116,7 @@ namespace osu.Game.Tests.Visual.Ranking
showPanel(score);
});
AddAssert("pp display faded out", () => this.ChildrenOfType<PerformanceStatistic>().Single().Alpha == 1);
AddUntilStep("pp display faded out", () => this.ChildrenOfType<PerformanceStatistic>().Single().Alpha == 1);
}
[Test]
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Online;
using osu.Game.Scoring;
@@ -12,7 +13,7 @@ namespace osu.Game.Tests.Visual.Ranking
{
public partial class TestSceneOverallRanking : OsuTestScene
{
private OverallRanking overallRanking = null!;
private readonly Bindable<ScoreBasedUserStatisticsUpdate?> statisticsUpdate = new Bindable<ScoreBasedUserStatisticsUpdate?>();
[Test]
public void TestUpdatePending()
@@ -104,14 +105,19 @@ namespace osu.Game.Tests.Visual.Ranking
displayUpdate(statistics, statistics);
}
private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking
private void createDisplay() => AddStep("create display", () =>
{
Width = 400,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
statisticsUpdate.Value = null;
Child = new OverallRanking(new ScoreInfo())
{
Width = 400,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
DisplayedUpdate = { BindTarget = statisticsUpdate }
};
});
private void displayUpdate(UserStatistics before, UserStatistics after) =>
AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
AddStep("display update", () => statisticsUpdate.Value = new ScoreBasedUserStatisticsUpdate(new ScoreInfo(), before, after));
}
}
@@ -4,7 +4,6 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
@@ -17,7 +16,6 @@ using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu;
@@ -406,7 +404,7 @@ namespace osu.Game.Tests.Visual.Ranking
: base(score)
{
AllowRetry = true;
ShowUserStatistics = true;
IsLocalPlay = true;
}
protected override void LoadComplete()
@@ -416,21 +414,19 @@ namespace osu.Game.Tests.Visual.Ranking
RetryOverlay = InternalChildren.OfType<HotkeyRetryOverlay>().SingleOrDefault();
}
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
protected override Task<ScoreInfo[]> FetchScores()
{
var scores = new List<ScoreInfo>();
var scores = new ScoreInfo[20];
for (int i = 0; i < 20; i++)
for (int i = 0; i < scores.Length; i++)
{
var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i;
score.HasOnlineReplay = true;
scores.Add(score);
scores[i] = score;
}
scoresCallback.Invoke(scores);
return null;
return Task.FromResult(scores);
}
}
@@ -446,27 +442,25 @@ namespace osu.Game.Tests.Visual.Ranking
this.fetchWaitTask = fetchWaitTask ?? Task.CompletedTask;
}
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
protected override Task<ScoreInfo[]> FetchScores()
{
Task.Run(async () =>
return Task.Run(async () =>
{
await fetchWaitTask;
var scores = new List<ScoreInfo>();
var scores = new ScoreInfo[20];
for (int i = 0; i < 20; i++)
for (int i = 0; i < scores.Length; i++)
{
var score = TestResources.CreateTestScoreInfo();
score.TotalScore += 10 - i;
scores.Add(score);
scores[i] = score;
}
scoresCallback?.Invoke(scores);
Schedule(() => FetchCompleted = true);
});
return null;
return scores;
});
}
}
@@ -11,6 +11,7 @@ using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@@ -18,6 +19,9 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
@@ -36,6 +40,8 @@ namespace osu.Game.Tests.Visual.Ranking
{
public partial class TestSceneStatisticsPanel : OsuTestScene
{
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
[Test]
public void TestScoreWithPositionStatistics()
{
@@ -137,62 +143,114 @@ namespace osu.Game.Tests.Visual.Ranking
{
CachedDependencies = [(typeof(UserStatisticsWatcher), userStatisticsWatcher)],
RelativeSizeAxes = Axes.Both,
Child = new UserStatisticsPanel(score)
Child = new StatisticsPanel
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Score = { Value = score, }
Score = { Value = score, },
AchievedScore = score,
}
});
AddUntilStep("overall ranking present", () => this.ChildrenOfType<OverallRanking>().Any());
AddUntilStep("loading spinner not visible", () => this.ChildrenOfType<LoadingLayer>().All(l => l.State.Value == Visibility.Hidden));
AddUntilStep("loading spinner not visible",
() => this.ChildrenOfType<OverallRanking>().Single()
.ChildrenOfType<LoadingLayer>().All(l => l.State.Value == Visibility.Hidden));
}
[Test]
public void TestTagging()
{
var score = TestResources.CreateTestScoreInfo();
AddStep("set up network requests", () =>
{
dummyAPI.HandleRequest = request =>
{
switch (request)
{
case ListTagsRequest listTagsRequest:
{
Scheduler.AddDelayed(() => listTagsRequest.TriggerSuccess(new APITagCollection
{
Tags =
[
new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", },
new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", },
new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", },
new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", },
]
}), 500);
return true;
}
case GetBeatmapSetRequest getBeatmapSetRequest:
{
var beatmapSet = CreateAPIBeatmapSet(score.BeatmapInfo);
beatmapSet.Beatmaps.Single().TopTags =
[
new APIBeatmapTag { TagId = 3, VoteCount = 9 },
];
Scheduler.AddDelayed(() => getBeatmapSetRequest.TriggerSuccess(beatmapSet), 500);
return true;
}
case AddBeatmapTagRequest:
case RemoveBeatmapTagRequest:
{
Scheduler.AddDelayed(request.TriggerSuccess, 500);
return true;
}
}
return false;
};
});
AddStep("load panel", () =>
{
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = new StatisticsPanel
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Score = { Value = score },
AchievedScore = score,
}
};
});
}
[Test]
public void TestTaggingWhenRankTooLow()
{
var score = TestResources.CreateTestScoreInfo();
score.Rank = ScoreRank.D;
AddStep("load panel", () =>
{
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = new StatisticsPanel
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Score = { Value = score },
AchievedScore = score,
}
};
});
}
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
{
Child = new UserStatisticsPanel(score)
Child = new StatisticsPanel
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Score = { Value = score },
DisplayedUserStatisticsUpdate =
{
Value = new ScoreBasedUserStatisticsUpdate(score, new UserStatistics
{
Level = new UserStatistics.LevelInfo
{
Current = 5,
Progress = 20,
},
GlobalRank = 38000,
CountryRank = 12006,
PP = 2134,
RankedScore = 21123849,
Accuracy = 0.985,
PlayCount = 13375,
PlayTime = 354490,
TotalScore = 128749597,
TotalHits = 0,
MaxCombo = 1233,
}, new UserStatistics
{
Level = new UserStatistics.LevelInfo
{
Current = 5,
Progress = 30,
},
GlobalRank = 36000,
CountryRank = 12000,
PP = (decimal)2134.5,
RankedScore = 23897015,
Accuracy = 0.984,
PlayCount = 13376,
PlayTime = 35789,
TotalScore = 132218497,
TotalHits = 0,
MaxCombo = 1233,
})
}
AchievedScore = score,
};
});
@@ -0,0 +1,85 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.Ranking;
namespace osu.Game.Tests.Visual.Ranking
{
public partial class TestSceneUserTagControl : OsuTestScene
{
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("set up working beatmap", () =>
{
Beatmap.Value.BeatmapInfo.OnlineID = 42;
});
AddStep("set up network requests", () =>
{
dummyAPI.HandleRequest = request =>
{
switch (request)
{
case ListTagsRequest listTagsRequest:
{
Scheduler.AddDelayed(() => listTagsRequest.TriggerSuccess(new APITagCollection
{
Tags =
[
new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", },
new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", },
new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", },
new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", },
]
}), 500);
return true;
}
case GetBeatmapSetRequest getBeatmapSetRequest:
{
var beatmapSet = CreateAPIBeatmapSet(Beatmap.Value.BeatmapInfo);
beatmapSet.Beatmaps.Single().TopTags =
[
new APIBeatmapTag { TagId = 3, VoteCount = 9 },
];
Scheduler.AddDelayed(() => getBeatmapSetRequest.TriggerSuccess(beatmapSet), 500);
return true;
}
case AddBeatmapTagRequest:
case RemoveBeatmapTagRequest:
{
Scheduler.AddDelayed(request.TriggerSuccess, 500);
return true;
}
}
return false;
};
});
AddStep("create control", () =>
{
Child = new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Child = new UserTagControl(Beatmap.Value.BeatmapInfo)
{
Width = 500,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
});
}
}
}
@@ -136,6 +136,13 @@ namespace osu.Game.Tests.Visual.Settings
public Bindable<float> Rotation { get; } = new Bindable<float>();
public BindableFloat PressureThreshold { get; } = new BindableFloat
{
MinValue = 0f,
MaxValue = 1f,
Precision = 0.005f,
};
public IBindable<TabletInfo> Tablet => tablet;
private readonly Bindable<TabletInfo> tablet = new Bindable<TabletInfo>();
@@ -12,6 +12,8 @@ using osu.Framework.Graphics;
using osu.Framework.Platform;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays;
@@ -20,14 +22,16 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Select;
using osu.Game.Screens.Select.Leaderboards;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.SongSelect
{
public partial class TestSceneBeatmapLeaderboard : OsuTestScene
public partial class TestSceneBeatmapLeaderboard : OsuManualInputManagerTestScene
{
private readonly FailableLeaderboard leaderboard;
@@ -37,6 +41,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private ScoreManager scoreManager = null!;
private RulesetStore rulesetStore = null!;
private BeatmapManager beatmapManager = null!;
private PlaySongSelect songSelect = null!;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
@@ -45,25 +50,36 @@ namespace osu.Game.Tests.Visual.SongSelect
dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm));
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
dependencies.CacheAs<Screens.Select.SongSelect>(songSelect = new PlaySongSelect());
Dependencies.Cache(Realm);
return dependencies;
}
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(songSelect);
}
public TestSceneBeatmapLeaderboard()
{
AddRange(new Drawable[]
Add(new OsuContextMenuContainer
{
dialogOverlay = new DialogOverlay
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
Depth = -1
},
leaderboard = new FailableLeaderboard
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = BeatmapLeaderboardScope.Global,
dialogOverlay = new DialogOverlay
{
Depth = -1
},
leaderboard = new FailableLeaderboard
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = BeatmapLeaderboardScope.Global,
}
}
});
}
@@ -165,6 +181,11 @@ namespace osu.Game.Tests.Visual.SongSelect
{
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global);
AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo())));
AddStep(@"New Scores with teams", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo()).Select(s =>
{
s.User.Team = new APITeam();
return s;
})));
}
[Test]
@@ -180,6 +201,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("ensure no scores displayed", () => leaderboard.SetScores(null));
AddStep(@"Network failure", () => leaderboard.SetErrorState(LeaderboardState.NetworkFailure));
AddStep(@"No team", () => leaderboard.SetErrorState(LeaderboardState.NoTeam));
AddStep(@"No supporter", () => leaderboard.SetErrorState(LeaderboardState.NotSupporter));
AddStep(@"Not logged in", () => leaderboard.SetErrorState(LeaderboardState.NotLoggedIn));
AddStep(@"Ruleset unavailable", () => leaderboard.SetErrorState(LeaderboardState.RulesetUnavailable));
@@ -187,6 +209,40 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep(@"None selected", () => leaderboard.SetErrorState(LeaderboardState.NoneSelected));
}
[Test]
public void TestUseTheseModsDoesNotCopySystemMods()
{
AddStep(@"set scores", () => leaderboard.SetScores(leaderboard.Scores, new ScoreInfo
{
Position = 999,
Rank = ScoreRank.XH,
Accuracy = 1,
MaxCombo = 244,
TotalScore = 1707827,
Ruleset = new OsuRuleset().RulesetInfo,
Mods = new Mod[] { new OsuModHidden(), new ModScoreV2(), },
User = new APIUser
{
Id = 6602580,
Username = @"waaiiru",
CountryCode = CountryCode.ES,
}
}));
AddUntilStep("wait for scores", () => this.ChildrenOfType<LeaderboardScore>().Count(), () => Is.GreaterThan(0));
AddStep("right click panel", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<LeaderboardScore>().Single());
InputManager.Click(MouseButton.Right);
});
AddStep("click use these mods", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableOsuMenuItem>().Single());
InputManager.Click(MouseButton.Left);
});
AddAssert("song select received HD", () => songSelect.Mods.Value.Any(m => m is OsuModHidden));
AddAssert("song select did not receive SV2", () => !songSelect.Mods.Value.Any(m => m is ModScoreV2));
}
private void showPersonalBestWithNullPosition()
{
leaderboard.SetScores(leaderboard.Scores, new ScoreInfo
@@ -423,7 +479,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Accuracy = 0.5140,
MaxCombo = 244,
TotalScore = 1707827,
Date = DateTime.Now.AddMonths(-3),
Date = DateTime.Now.AddMonths(-10),
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
BeatmapInfo = beatmapInfo,
BeatmapHash = beatmapInfo.Hash,
@@ -6,16 +6,17 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing;
using osu.Game.Graphics.Cursor;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.SongSelectV2
{
public abstract partial class SongSelectComponentsTestScene : OsuTestScene
public abstract partial class SongSelectComponentsTestScene : OsuManualInputManagerTestScene
{
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
protected override Container<Drawable> Content { get; } = new Container
protected override Container<Drawable> Content { get; } = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@@ -7,9 +7,11 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Rulesets.Mania;
@@ -22,6 +24,7 @@ using osu.Game.Screens.SelectV2.Leaderboards;
using osu.Game.Tests.Resources;
using osu.Game.Users;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.SongSelectV2
{
@@ -102,6 +105,69 @@ namespace osu.Game.Tests.Visual.SongSelectV2
});
}
[Test]
public void TestUseTheseModsDoesNotCopySystemMods()
{
LeaderboardScoreV2 score = null!;
AddStep("create content", () =>
{
Children = new Drawable[]
{
fillFlow = new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0f, 2f),
Shear = new Vector2(OsuGame.SHEAR, 0)
},
drawWidthText = new OsuSpriteText(),
};
var scoreInfo = new ScoreInfo
{
Position = 999,
Rank = ScoreRank.X,
Accuracy = 1,
MaxCombo = 244,
TotalScore = RNG.Next(1_800_000, 2_000_000),
MaximumStatistics = { { HitResult.Great, 3000 } },
Mods = new Mod[] { new OsuModHidden(), new ModScoreV2(), },
Ruleset = new OsuRuleset().RulesetInfo,
User = new APIUser
{
Id = 6602580,
Username = @"waaiiru",
CountryCode = CountryCode.ES,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
},
Date = DateTimeOffset.Now.AddYears(-2),
};
fillFlow.Add(score = new LeaderboardScoreV2(scoreInfo)
{
Rank = scoreInfo.Position,
Shear = Vector2.Zero,
});
score.Show();
});
AddStep("right click panel", () =>
{
InputManager.MoveMouseTo(score);
InputManager.Click(MouseButton.Right);
});
AddStep("click use these mods", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<DrawableOsuMenuItem>().Single());
InputManager.Click(MouseButton.Left);
});
AddAssert("mods received HD", () => score.SelectedMods.Value.Any(m => m is OsuModHidden));
AddAssert("mods did not receive SV2", () => !score.SelectedMods.Value.Any(m => m is ModScoreV2));
}
public override void SetUpSteps()
{
AddToggleStep("toggle scoring mode", v => config.SetValue(OsuSetting.ScoreDisplayMode, v ? ScoringMode.Classic : ScoringMode.Standardised));
@@ -9,16 +9,10 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mods;
@@ -29,7 +23,6 @@ using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu;
using osu.Game.Screens.SelectV2.Footer;
using osu.Game.Tests.Resources;
using osuTK.Input;
namespace osu.Game.Tests.Visual.SongSelectV2
@@ -42,8 +35,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
[Cached]
private readonly OsuLogo logo;
private BeatmapManager beatmapManager = null!;
protected override bool UseOnlineAPI => true;
public TestSceneSongSelect()
@@ -66,32 +57,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
}
[BackgroundDependencyLoader]
private void load(GameHost host, IAPIProvider onlineAPI)
private void load()
{
BeatmapStore beatmapStore;
BeatmapUpdater beatmapUpdater;
BeatmapDifficultyCache difficultyCache;
RealmDetachedBeatmapStore beatmapStore;
// These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install.
// At a point we have isolated interactive test runs enough, this can likely be removed.
Dependencies.Cache(new RealmRulesetStore(Realm));
Dependencies.Cache(Realm);
Dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, onlineAPI, Audio, Resources, host, Beatmap.Default, difficultyCache));
Dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(beatmapManager, difficultyCache, onlineAPI, LocalStorage));
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
beatmapManager.ProcessBeatmap = (set, scope) => beatmapUpdater.Process(set, scope);
MusicController music;
Dependencies.Cache(music = new MusicController());
// required to get bindables attached
Add(difficultyCache);
Add(music);
Dependencies.CacheAs<BeatmapStore>(beatmapStore = new RealmDetachedBeatmapStore());
Add(beatmapStore);
Dependencies.Cache(new OsuConfigManager(LocalStorage));
}
protected override void LoadComplete()
@@ -107,9 +78,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
base.SetUpSteps();
AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SongSelectV2()));
AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelectV2 songSelect && songSelect.IsLoaded);
AddStep("import test beatmap", () => beatmapManager.Import(TestResources.GetTestBeatmapForImport()));
AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SoloSongSelect()));
AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelect songSelect && songSelect.IsLoaded);
}
[Test]
@@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
base.SetUpSteps();
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddWaitStep("wait", 5);
PushAndConfirm(() => new Screens.SelectV2.SongSelectV2());
PushAndConfirm(() => new Screens.SelectV2.SoloSongSelect());
}
[Test]
@@ -11,7 +11,6 @@ using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Tests.Visual.OnlinePlay;
@@ -26,15 +25,12 @@ namespace osu.Game.Tests.Visual
/// <item>Provides a <see cref="TestMultiplayerClient"/> to be resolved as a dependency in the <see cref="Screens.OnlinePlay.Multiplayer.Multiplayer"/> screen,
/// which is typically a part of <see cref="OsuGameBase"/>.</item>
/// <item>Rebinds the <see cref="DummyAPIAccess"/> to handle requests via a <see cref="TestRoomRequestsHandler"/>.</item>
/// <item>Provides a <see cref="TestMultiplayerRoomManager"/> for the <see cref="Screens.OnlinePlay.Multiplayer.Multiplayer"/> screen.</item>
/// </list>
/// </p>
/// </summary>
public partial class TestMultiplayerComponents : OsuScreen
{
public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen => multiplayerScreen;
public TestMultiplayerRoomManager RoomManager => multiplayerScreen.RoomManager;
public Screens.OnlinePlay.Multiplayer.Multiplayer MultiplayerScreen { get; }
public IScreen CurrentScreen => screenStack.CurrentScreen;
@@ -53,17 +49,17 @@ namespace osu.Game.Tests.Visual
private BeatmapManager beatmapManager { get; set; }
private readonly OsuScreenStack screenStack;
private readonly TestMultiplayer multiplayerScreen;
private readonly TestRoomRequestsHandler requestsHandler = new TestRoomRequestsHandler();
public TestMultiplayerComponents()
{
multiplayerScreen = new TestMultiplayer();
MultiplayerScreen = new Screens.OnlinePlay.Multiplayer.Multiplayer();
InternalChildren = new Drawable[]
{
userLookupCache,
beatmapLookupCache,
MultiplayerClient = new TestMultiplayerClient(RoomManager),
MultiplayerClient = new TestMultiplayerClient(requestsHandler),
screenStack = new OsuScreenStack
{
Name = nameof(TestMultiplayerComponents),
@@ -71,13 +67,13 @@ namespace osu.Game.Tests.Visual
}
};
screenStack.Push(multiplayerScreen);
screenStack.Push(MultiplayerScreen);
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager);
((DummyAPIAccess)api).HandleRequest = request => requestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager);
}
public override bool OnBackButton() => (screenStack.CurrentScreen as OsuScreen)?.OnBackButton() ?? base.OnBackButton();
@@ -90,13 +86,5 @@ namespace osu.Game.Tests.Visual
screenStack.Exit();
return true;
}
private partial class TestMultiplayer : Screens.OnlinePlay.Multiplayer.Multiplayer
{
public new TestMultiplayerRoomManager RoomManager { get; private set; }
public TestRoomRequestsHandler RequestsHandler { get; private set; }
protected override RoomManager CreateRoomManager() => RoomManager = new TestMultiplayerRoomManager(RequestsHandler = new TestRoomRequestsHandler());
}
}
}
@@ -1,16 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Metadata;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard.Friends;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.UserInterface
{
@@ -19,37 +20,90 @@ namespace osu.Game.Tests.Visual.UserInterface
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
private FriendOnlineStreamControl control;
private TestMetadataClient metadataClient = null!;
[SetUp]
public void SetUp() => Schedule(() => Child = control = new FriendOnlineStreamControl
public void SetUp() => Schedule(() =>
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies =
[
(typeof(MetadataClient), metadataClient = new TestMetadataClient())
],
Children = new Drawable[]
{
metadataClient,
new FriendOnlineStreamControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
}
};
});
[Test]
public void Populate()
public void TestChangeFriends()
{
AddStep("Populate", () => control.Populate(new List<APIUser>
AddStep("set 10 friends", () =>
{
new APIUser
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
{
IsOnline = true
},
new APIUser
{
IsOnline = false
},
new APIUser
{
IsOnline = false
}
}));
RelationType = RelationType.Friend,
TargetID = i,
TargetUser = new APIUser { Id = i },
}));
});
AddAssert("3 users", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.All)?.Count == 3);
AddAssert("1 online user", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.Online)?.Count == 1);
AddAssert("2 offline users", () => control.Items.FirstOrDefault(item => item.Status == OnlineStatus.Offline)?.Count == 2);
AddStep("set 20 friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(Enumerable.Range(1, 20).Select(i => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = i,
TargetUser = new APIUser { Id = i },
}));
});
}
[Test]
public void TestChangeOnlineStates()
{
AddStep("set 10 friends", () =>
{
DummyAPIAccess api = (DummyAPIAccess)API;
api.Friends.Clear();
api.Friends.AddRange(Enumerable.Range(1, 10).Select(i => new APIRelation
{
RelationType = RelationType.Friend,
TargetID = i,
TargetUser = new APIUser { Id = i },
}));
});
AddStep("make users 1-5 online", () =>
{
for (int i = 1; i <= 5; i++)
metadataClient.FriendPresenceUpdated(i, new UserPresence { Status = UserStatus.Online });
});
AddStep("make users 1-5 DnD", () =>
{
for (int i = 1; i <= 5; i++)
metadataClient.FriendPresenceUpdated(i, new UserPresence { Status = UserStatus.DoNotDisturb });
});
AddStep("make users 1-5 offline", () =>
{
for (int i = 1; i <= 5; i++)
metadataClient.FriendPresenceUpdated(i, null);
});
}
}
}
@@ -1030,7 +1030,10 @@ namespace osu.Game.Tests.Visual.UserInterface
private partial class TestModSelectOverlay : UserModSelectOverlay
{
protected override bool ShowPresets => true;
public TestModSelectOverlay()
{
ShowPresets = true;
}
}
private class TestUnimplementedMod : Mod
@@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
private DependencyProvidingContainer contentContainer = null!;
private ScreenFooter screenFooter = null!;
private TestModSelectOverlay modOverlay = null!;
private UserModSelectOverlay modOverlay = null!;
[SetUp]
public void SetUp() => Schedule(() =>
@@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface
},
Children = new Drawable[]
{
modOverlay = new TestModSelectOverlay(),
modOverlay = new UserModSelectOverlay { ShowPresets = true },
new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
@@ -196,11 +196,6 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("external overlay content still not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
}
private partial class TestModSelectOverlay : UserModSelectOverlay
{
protected override bool ShowPresets => true;
}
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
{
public TestShearedOverlayContainer()
@@ -115,11 +115,10 @@ namespace osu.Game.Tests.Visual.UserInterface
private partial class TestModSelectOverlay : UserModSelectOverlay
{
protected override bool ShowPresets => true;
public TestModSelectOverlay()
: base(OverlayColourScheme.Aquamarine)
{
ShowPresets = true;
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
namespace osu.Game.Beatmaps
{
public class APIBeatmapTag
{
[JsonProperty("tag_id")]
public long TagId { get; set; }
[JsonProperty("count")]
public int VoteCount { get; set; }
}
}
@@ -1,15 +1,28 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Localisation;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Select;
namespace osu.Game.Beatmaps
{
public static class BeatmapInfoExtensions
{
/// <summary>
/// Given an <see cref="IBeatmap"/>, update length, BPM and object counts.
/// </summary>
public static void UpdateStatisticsFromBeatmap(this BeatmapInfo beatmapInfo, IBeatmap beatmap)
{
beatmapInfo.Length = beatmap.CalculatePlayableLength();
beatmapInfo.BPM = 60000 / beatmap.GetMostCommonBeatLength();
beatmapInfo.EndTimeObjectCount = beatmap.HitObjects.Count(h => h is IHasDuration);
beatmapInfo.TotalObjectCount = beatmap.HitObjects.Count;
}
/// <summary>
/// A user-presentable display title representing this beatmap.
/// </summary>
+2 -5
View File
@@ -51,7 +51,7 @@ namespace osu.Game.Beatmaps
if (lookupScope != MetadataLookupScope.None)
metadataLookup.Update(beatmapSet, lookupScope == MetadataLookupScope.OnlineFirst);
foreach (var beatmap in beatmapSet.Beatmaps)
foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps)
{
difficultyCache.Invalidate(beatmap);
@@ -63,10 +63,7 @@ namespace osu.Game.Beatmaps
var calculator = ruleset.CreateDifficultyCalculator(working);
beatmap.StarRating = calculator.Calculate().StarRating;
beatmap.Length = working.Beatmap.CalculatePlayableLength();
beatmap.BPM = 60000 / working.Beatmap.GetMostCommonBeatLength();
beatmap.EndTimeObjectCount = working.Beatmap.HitObjects.Count(h => h is IHasDuration);
beatmap.TotalObjectCount = working.Beatmap.HitObjects.Count;
beatmap.UpdateStatisticsFromBeatmap(working.Beatmap);
}
// And invalidate again afterwards as re-fetching the most up-to-date database metadata will be required.

Some files were not shown because too many files have changed in this diff Show More