1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-13 20:33:35 +08:00

Merge branch 'master' into custom-sample-set-selection

This commit is contained in:
Dean Herbert
2025-12-17 19:30:24 +09:00
committed by GitHub
Unverified
303 changed files with 6304 additions and 2423 deletions
+1 -1
View File
@@ -21,7 +21,7 @@
]
},
"ppy.localisationanalyser.tools": {
"version": "2024.802.0",
"version": "2025.1208.0",
"commands": [
"localisation"
]
+3
View File
@@ -73,6 +73,9 @@ Aside from the above, below is a brief checklist of things to watch out when you
After you're done with your changes and you wish to open the PR, please observe the following recommendations:
- Please submit the pull request from a [topic branch](https://git-scm.com/book/en/v2/Git-Branching-Branching-Workflows#_topic_branch) (not `master`), and keep the *Allow edits from maintainers* check box selected, so that we can push fixes to your PR if necessary.
- Please pick the following target branch for your pull request:
- `pp-dev`, if the change impacts star rating or performance points calculations for any of the rulesets,
- `master`, otherwise.
- Please avoid pushing untested or incomplete code.
- Please do not force-push or rebase unless we ask you to.
- Please do not merge `master` continually if there are no conflicts to resolve. We will do this for you when the change is ready for merge.
+1 -1
View File
@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1021.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1209.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
+1 -1
View File
@@ -189,7 +189,7 @@ namespace osu.Desktop
}
// user party
if (!hideIdentifiableInformation && multiplayerClient.Room != null)
if (!hideIdentifiableInformation && multiplayerClient.Room != null && multiplayerClient.Room.Settings.MatchType != MatchType.Matchmaking)
{
MultiplayerRoom room = multiplayerClient.Room;
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private double placementStartTime;
private double placementEndTime;
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(HitObject.Duration, 0));
public BananaShowerPlacementBlueprint()
{
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private InputManager inputManager = null!;
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(HitObject.Duration, 0));
public JuiceStreamPlacementBlueprint()
{
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using osu.Framework.Allocation;
@@ -224,7 +225,8 @@ namespace osu.Game.Rulesets.Catch.Edit
#region Clipboard handling
public override string ConvertSelectionToString()
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<CatchHitObject>().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString()));
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<CatchHitObject>().OrderBy(h => h.StartTime)
.Select(h => (h.IndexInCurrentCombo + 1).ToString(CultureInfo.InvariantCulture)));
// 1,2,3,4 ...
private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled);
@@ -72,6 +72,9 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
leaderboard.Origin = Anchor.CentreLeft;
leaderboard.X = 10;
}
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
})
{
Children = new Drawable[]
@@ -5,6 +5,7 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Testing;
using osu.Game.Rulesets.Edit;
@@ -16,6 +17,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Screens.Edit;
using osu.Game.Tests.Visual;
using osuTK.Input;
@@ -36,21 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
[Test]
public void TestPlaceBeforeCurrentTimeDownwards()
{
AddStep("seek to 200", () => HitObjectContainer.Dependencies.Get<EditorClock>().Seek(200));
AddStep("move mouse before current time", () =>
{
var column = this.ChildrenOfType<Column>().Single();
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(-100));
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("note start time < 0", () => getNote().StartTime < 0);
}
[Test]
public void TestPlaceAfterCurrentTimeDownwards()
{
AddStep("move mouse after current time", () =>
{
var column = this.ChildrenOfType<Column>().Single();
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(100));
@@ -58,7 +47,22 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("note start time > 0", () => getNote().StartTime > 0);
AddAssert("note start time < 200", () => getNote().StartTime < 200);
}
[Test]
public void TestPlaceAfterCurrentTimeDownwards()
{
AddStep("seek to 200", () => HitObjectContainer.Dependencies.Get<EditorClock>().Seek(200));
AddStep("move mouse after current time", () =>
{
var column = this.ChildrenOfType<Column>().Single();
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(300));
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("note start time > 200", () => getNote().StartTime > 200);
}
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
@@ -18,15 +18,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
public void TestNormalSelection()
{
addStepClickLink("00:05:920 (5920|3,6623|3,6857|2,7326|1)");
AddAssert("selected group", () => checkSnapAndSelectColumn(5_920, new List<(int, int)>
{ (5_920, 3), (6_623, 3), (6_857, 2), (7_326, 1) }
));
AddAssert("selected group", () => checkSnapAndSelectColumn(5_920, [(5_920, 3), (6_623, 3), (6_857, 2), (7_326, 1)]));
addReset();
addStepClickLink("00:42:716 (42716|3,43420|2,44123|0,44357|1,45295|1)");
AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(42_716, new List<(int, int)>
{ (42_716, 3), (43_420, 2), (44_123, 0), (44_357, 1), (45_295, 1) }
));
AddAssert("selected ungrouped", () => checkSnapAndSelectColumn(42_716, [(42_716, 3), (43_420, 2), (44_123, 0), (44_357, 1), (45_295, 1)]));
addReset();
AddStep("add notes to row", () =>
@@ -41,15 +37,20 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
EditorBeatmap.AddRange(new[] { second, third, forth });
});
addStepClickLink("00:11:545 (11545|0,11545|1,11545|2,11545|3)");
AddAssert("selected in row", () => checkSnapAndSelectColumn(11_545, new List<(int, int)>
{ (11_545, 0), (11_545, 1), (11_545, 2), (11_545, 3) }
));
AddAssert("selected in row", () => checkSnapAndSelectColumn(11_545, [(11_545, 0), (11_545, 1), (11_545, 2), (11_545, 3)]));
addReset();
addStepClickLink("01:36:623 (96623|1,97560|1,97677|1,97795|1,98966|1)");
AddAssert("selected in column", () => checkSnapAndSelectColumn(96_623, new List<(int, int)>
{ (96_623, 1), (97_560, 1), (97_677, 1), (97_795, 1), (98_966, 1) }
));
AddAssert("selected in column", () => checkSnapAndSelectColumn(96_623, [(96_623, 1), (97_560, 1), (97_677, 1), (97_795, 1), (98_966, 1)]));
}
[Test]
public void TestRoundingToNearestMillisecondApplied()
{
AddStep("resnap note to have fractional coordinates",
() => EditorBeatmap.HitObjects.OfType<ManiaHitObject>().Single(ho => ho.StartTime == 85_373 && ho.Column == 1).StartTime = 85_373.125);
addStepClickLink("01:25:373 (85373|1)");
AddAssert("selected note", () => checkSnapAndSelectColumn(85_373.125, [(85_373.125, 1)]));
}
[Test]
@@ -75,7 +76,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
private void addReset() => addStepClickLink("00:00:000", "reset", false);
private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(int, int)>? columnPairs = null)
private bool checkSnapAndSelectColumn(double startTime, IReadOnlyCollection<(double, int)>? columnPairs = null)
{
bool checkColumns = columnPairs != null
? EditorBeatmap.SelectedHitObjects.All(x => columnPairs.Any(col => isNoteAt(x, col.Item1, col.Item2)))
@@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Mania.Tests
[TestCase("mania-samples")]
[TestCase("mania-slider")] // e.g. second and fourth notes of https://osu.ppy.sh/beatmapsets/73883#mania/216407
[TestCase("slider-convert-samples")]
[TestCase("spinner-convert-samples")]
public void Test(string name) => base.Test(name);
protected override IEnumerable<SampleConvertValue> CreateConvertValue(HitObject hitObject)
@@ -0,0 +1,10 @@
osu file format v14
[General]
Mode: 0
[TimingPoints]
0,300,4,0,2,100,1,0
[HitObjects]
444,320,1000,5,2,0:0:0:0:
@@ -0,0 +1,16 @@
{
"Mappings": [{
"StartTime": 1000.0,
"Objects": [{
"StartTime": 1000.0,
"EndTime": 8000.0,
"Column": 0,
"PlaySlidingSamples": false,
"NodeSamples": [
["Gameplay/soft-hitnormal"],
["Gameplay/soft-hitnormal", "Gameplay/soft-hitfinish"]
],
"Samples": ["Gameplay/soft-hitnormal", "Gameplay/soft-hitfinish"],
}]
}]
}
@@ -0,0 +1,18 @@
osu file format v14
[General]
Mode: 0
[Difficulty]
HPDrainRate:5
CircleSize:5
OverallDifficulty:5
ApproachRate:5
SliderMultiplier:1.4
SliderTickRate:1
[TimingPoints]
0,500,4,2,0,100,1,0
[HitObjects]
256,192,1000,8,4,8000,0:2:0:0:
@@ -45,5 +45,19 @@ namespace osu.Game.Rulesets.Mania.Tests
AssertBeatmapLookup(expected_sample);
AssertNoLookup(unwanted_sample);
}
[Test]
public void TestConvertHitObjectCustomSampleBank()
{
const string beatmap_sample = "normal-hitwhistle2";
const string user_skin_sample = "normal-hitnormal";
SetupSkins(beatmap_sample, user_skin_sample);
CreateTestWithBeatmap("convert-beatmap-custom-sample-bank.osu");
AssertBeatmapLookup(beatmap_sample);
AssertUserLookup(user_skin_sample);
}
}
}
@@ -85,7 +85,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
Duration = endTime - HitObject.StartTime,
Column = column,
Samples = HitObject.Samples,
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
NodeSamples =
[
HitObject.Samples.Where(s => s.Name == HitSampleInfo.HIT_NORMAL).ToList(),
HitObject.Samples
]
};
}
else
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved]
private IScrollingInfo scrollingInfo { get; set; } = null!;
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(HitObject.Duration, 0);
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(HitObject.Duration, 0));
public HoldNotePlacementBlueprint()
: base(new HoldNote())
@@ -1,10 +1,12 @@
// 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.Text.RegularExpressions;
using osu.Framework.Allocation;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
@@ -54,7 +56,8 @@ namespace osu.Game.Rulesets.Mania.Edit
};
public override string ConvertSelectionToString()
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<ManiaHitObject>().OrderBy(h => h.StartTime).Select(h => $"{h.StartTime}|{h.Column}"));
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<ManiaHitObject>().OrderBy(h => h.StartTime)
.Select(h => FormattableString.Invariant($"{Math.Round(h.StartTime)}|{h.Column}")));
// 123|0,456|1,789|2 ...
private static readonly Regex selection_regex = new Regex(@"^\d+\|\d+(,\d+\|\d+)*$", RegexOptions.Compiled);
@@ -73,10 +76,10 @@ namespace osu.Game.Rulesets.Mania.Edit
if (split.Length != 2)
continue;
if (!double.TryParse(split[0], out double time) || !int.TryParse(split[1], out int column))
if (!int.TryParse(split[0], out int time) || !int.TryParse(split[1], out int column))
continue;
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => h.StartTime == time && h.Column == column);
ManiaHitObject? current = remainingHitObjects.FirstOrDefault(h => Precision.AlmostEquals(h.StartTime, time, 0.5) && h.Column == column);
if (current == null)
continue;
@@ -7,5 +7,15 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDifficultyAdjust : ModDifficultyAdjust
{
public override DifficultyBindable OverallDifficulty { get; } = new DifficultyBindable
{
Precision = 0.1f,
MinValue = 0,
MaxValue = 10,
// Use larger extended limits for mania to include OD values that occur with EZ or HR enabled
ExtendedMaxValue = 15,
ExtendedMinValue = -15,
ReadCurrentFromDifficulty = diff => diff.OverallDifficulty,
};
}
}
@@ -57,6 +57,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
if (spectatorList != null)
spectatorList.Position = new Vector2(36, -66);
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
})
{
new DrawableGameplayLeaderboard(),
@@ -64,11 +64,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private readonly Lazy<bool> hasKeyTexture;
private readonly ManiaBeatmap beatmap;
private readonly bool isBeatmapConverted;
public ManiaLegacySkinTransformer(ISkin skin, IBeatmap beatmap)
: base(skin)
{
this.beatmap = (ManiaBeatmap)beatmap;
isBeatmapConverted = !beatmap.BeatmapInfo.Ruleset.Equals(new ManiaRuleset().RulesetInfo);
isLegacySkin = new Lazy<bool>(() => GetConfig<SkinConfiguration.LegacySetting, decimal>(SkinConfiguration.LegacySetting.Version) != null);
hasKeyTexture = new Lazy<bool>(() =>
@@ -120,6 +122,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
leaderboard.Origin = Anchor.CentreLeft;
leaderboard.X = 10;
}
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
})
{
new LegacyManiaComboCounter(),
@@ -196,8 +201,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
public override ISample GetSample(ISampleInfo sampleInfo)
{
// layered hit sounds never play in mania
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered)
// layered hit sounds never play in mania-native beatmaps (but do play on converts)
if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered && !isBeatmapConverted)
return new SampleVirtual();
return base.GetSample(sampleInfo);
@@ -245,13 +245,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("grid spacing is distance to slider tail", () =>
{
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01)
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.1)
&& Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y);
});
AddAssert("grid rotation points to slider tail", () =>
{
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01);
return Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.1);
});
AddStep("start grid placement", () => InputManager.Key(Key.Number5));
@@ -280,9 +280,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("grid spacing and rotation unchanged", () =>
{
var composer = Editor.ChildrenOfType<RectangularPositionSnapGrid>().Single();
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.01)
return Precision.AlmostEquals(composer.Spacing.Value.X, 32.05, 0.1)
&& Precision.AlmostEquals(composer.Spacing.Value.X, composer.Spacing.Value.Y)
&& Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.01);
&& Precision.AlmostEquals(composer.GridLineRotation.Value, 0.09, 0.1);
});
}
@@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.UI;
@@ -22,7 +23,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[TestFixture]
public partial class TestSceneSliderDrawing : TestSceneOsuEditor
{
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new TestBeatmap(ruleset, false);
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
return beatmap;
}
[Test]
public void TestTouchInputPlaceHitCircleDirectly()
@@ -0,0 +1,52 @@
// 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.Allocation;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests
{
public partial class TestSceneGameplayCursorSizeChange : PlayerTestScene
{
private const float initial_cursor_size = 1f;
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
[Resolved]
private SkinManager? skins { get; set; }
[BackgroundDependencyLoader]
private void load()
{
if (skins != null) skins.CurrentSkinInfo.Value = skins.DefaultClassicSkin.SkinInfo;
}
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("Set gameplay cursor size: 1", () => LocalConfig.SetValue(OsuSetting.GameplayCursorSize, initial_cursor_size));
AddStep("resume player", () => Player.GameplayClockContainer.Start());
AddUntilStep("clock running", () => Player.GameplayClockContainer.IsRunning);
}
[Test]
public void TestPausedChangeCursorSize()
{
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddStep("move cursor to top left", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft));
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
AddStep("move cursor to top right", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopRight));
AddStep("press escape", () => InputManager.Key(Key.Escape));
AddSliderStep("cursor size", 0.1f, 2f, 1f, v => LocalConfig.SetValue(OsuSetting.GameplayCursorSize, v));
}
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(true, false);
}
}
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
@@ -10,6 +11,7 @@ using osu.Framework.Input.States;
using osu.Framework.Logging;
using osu.Framework.Testing.Input;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
@@ -58,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests
foreach (var smokeContainer in smokeContainers)
{
if (smokeContainer.Children.Count != 0)
if (smokeContainer.Children.OfType<SkinnableDrawable>().Any())
return false;
}
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
this.gridToolboxGroup = gridToolboxGroup;
originalOrigin = gridToolboxGroup.StartPosition.Value;
originalSpacing = gridToolboxGroup.Spacing.Value;
originalSpacing = gridToolboxGroup.GridLineSpacing.Value;
originalRotation = gridToolboxGroup.GridLinesRotation.Value;
}
@@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
// Reset the grid to the default values.
gridToolboxGroup.StartPosition.Value = gridToolboxGroup.StartPosition.Default;
gridToolboxGroup.Spacing.Value = gridToolboxGroup.Spacing.Default;
gridToolboxGroup.GridLineSpacing.Value = gridToolboxGroup.GridLineSpacing.Default;
if (!gridToolboxGroup.GridLinesRotation.Disabled)
gridToolboxGroup.GridLinesRotation.Value = gridToolboxGroup.GridLinesRotation.Default;
EndPlacement(true);
@@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
// Default to the original spacing and rotation if the distance is too small.
if (Vector2.Distance(gridToolboxGroup.StartPosition.Value, pos) < 2)
{
gridToolboxGroup.Spacing.Value = originalSpacing;
gridToolboxGroup.GridLineSpacing.Value = originalSpacing;
if (!gridToolboxGroup.GridLinesRotation.Disabled)
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
}
@@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
private void resetGridState()
{
gridToolboxGroup.StartPosition.Value = originalOrigin;
gridToolboxGroup.Spacing.Value = originalSpacing;
gridToolboxGroup.GridLineSpacing.Value = originalSpacing;
if (!gridToolboxGroup.GridLinesRotation.Disabled)
gridToolboxGroup.GridLinesRotation.Value = originalRotation;
}
@@ -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.HasValidLengthForPlacement;
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || HitObject.Path.HasValidLengthForPlacement);
public SliderPlacementBlueprint()
: base(new Slider())
@@ -5,8 +5,10 @@ using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit
{
@@ -42,25 +44,31 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly BindableInt displayTolerance = new BindableInt(90)
{
MinValue = 5,
MaxValue = 100
MaxValue = 100,
Precision = 1,
};
private readonly BindableInt displayCornerThreshold = new BindableInt(40)
{
MinValue = 5,
MaxValue = 100
MaxValue = 100,
Precision = 1,
};
private readonly BindableInt displayCircleThreshold = new BindableInt(30)
{
MinValue = 0,
MaxValue = 100
MaxValue = 100,
Precision = 1,
};
private ExpandableSlider<int> toleranceSlider = null!;
private ExpandableSlider<int> cornerThresholdSlider = null!;
private ExpandableSlider<int> circleThresholdSlider = null!;
[Resolved]
private IExpandingContainer? expandingContainer { get; set; }
[BackgroundDependencyLoader]
private void load()
{
@@ -68,15 +76,18 @@ namespace osu.Game.Rulesets.Osu.Edit
{
toleranceSlider = new ExpandableSlider<int>
{
Current = displayTolerance
Current = displayTolerance,
ExpandedLabelText = "Control point spacing",
},
cornerThresholdSlider = new ExpandableSlider<int>
{
Current = displayCornerThreshold
Current = displayCornerThreshold,
ExpandedLabelText = "Corner bias",
},
circleThresholdSlider = new ExpandableSlider<int>
{
Current = displayCircleThreshold
Current = displayCircleThreshold,
ExpandedLabelText = "Perfect curve bias"
}
};
}
@@ -88,24 +99,18 @@ namespace osu.Game.Rulesets.Osu.Edit
displayTolerance.BindValueChanged(tolerance =>
{
toleranceSlider.ContractedLabelText = $"C. P. S.: {tolerance.NewValue:N0}";
toleranceSlider.ExpandedLabelText = $"Control Point Spacing: {tolerance.NewValue:N0}";
Tolerance.Value = displayToInternalTolerance(tolerance.NewValue);
}, true);
displayCornerThreshold.BindValueChanged(threshold =>
{
cornerThresholdSlider.ContractedLabelText = $"C. T.: {threshold.NewValue:N0}";
cornerThresholdSlider.ExpandedLabelText = $"Corner Threshold: {threshold.NewValue:N0}";
cornerThresholdSlider.ContractedLabelText = $"C. B.: {threshold.NewValue:N0}";
CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue);
}, true);
displayCircleThreshold.BindValueChanged(threshold =>
{
circleThresholdSlider.ContractedLabelText = $"P. C. T.: {threshold.NewValue:N0}";
circleThresholdSlider.ExpandedLabelText = $"Perfect Curve Threshold: {threshold.NewValue:N0}";
circleThresholdSlider.ContractedLabelText = $"P. C. B.: {threshold.NewValue:N0}";
CircleThreshold.Value = displayToInternalCircleThreshold(threshold.NewValue);
}, true);
@@ -119,6 +124,11 @@ namespace osu.Game.Rulesets.Osu.Edit
displayCircleThreshold.Value = internalToDisplayCircleThreshold(threshold.NewValue)
);
expandingContainer?.Expanded.BindValueChanged(v =>
{
Spacing = v.NewValue ? new Vector2(5) : new Vector2(15);
}, true);
float displayToInternalTolerance(float v) => v / 50f;
int internalToDisplayTolerance(float v) => (int)Math.Round(v * 50f);
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
MinValue = 0f,
MaxValue = OsuPlayfield.BASE_SIZE.X,
Precision = 0.01f,
Precision = 0.1f,
};
/// <summary>
@@ -48,17 +48,17 @@ namespace osu.Game.Rulesets.Osu.Edit
{
MinValue = 0f,
MaxValue = OsuPlayfield.BASE_SIZE.Y,
Precision = 0.01f,
Precision = 0.1f,
};
/// <summary>
/// The spacing between grid lines.
/// </summary>
public BindableFloat Spacing { get; } = new BindableFloat(4f)
public BindableFloat GridLineSpacing { get; } = new BindableFloat(4f)
{
MinValue = 4f,
MaxValue = 256f,
Precision = 0.01f,
Precision = 0.1f,
};
/// <summary>
@@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
MinValue = -180f,
MaxValue = 180f,
Precision = 0.01f,
Precision = 0.1f,
};
/// <summary>
@@ -115,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Edit
float dist = Vector2.Distance(point1, point2);
while (dist >= max_automatic_spacing)
dist /= 2;
Spacing.Value = dist;
GridLineSpacing.Value = dist;
}
[BackgroundDependencyLoader]
@@ -127,21 +127,25 @@ namespace osu.Game.Rulesets.Osu.Edit
{
Current = StartPositionX,
KeyboardStep = 1,
ExpandedLabelText = "X offset",
},
startPositionYSlider = new ExpandableSlider<float>
{
Current = StartPositionY,
KeyboardStep = 1,
ExpandedLabelText = "Y offset",
},
spacingSlider = new ExpandableSlider<float>
{
Current = Spacing,
Current = GridLineSpacing,
KeyboardStep = 1,
ExpandedLabelText = "Spacing",
},
gridLinesRotationSlider = new ExpandableSlider<float>
{
Current = GridLinesRotation,
KeyboardStep = 1,
ExpandedLabelText = "Rotation",
},
new FillFlowContainer
{
@@ -170,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Edit
},
};
Spacing.Value = editorBeatmap.GridSize;
GridLineSpacing.Value = editorBeatmap.GridSize;
}
protected override void LoadComplete()
@@ -182,14 +186,12 @@ namespace osu.Game.Rulesets.Osu.Edit
StartPositionX.BindValueChanged(x =>
{
startPositionXSlider.ContractedLabelText = $"X: {x.NewValue:#,0.##}";
startPositionXSlider.ExpandedLabelText = $"X Offset: {x.NewValue:#,0.##}";
StartPosition.Value = new Vector2(x.NewValue, StartPosition.Value.Y);
}, true);
StartPositionY.BindValueChanged(y =>
{
startPositionYSlider.ContractedLabelText = $"Y: {y.NewValue:#,0.##}";
startPositionYSlider.ExpandedLabelText = $"Y Offset: {y.NewValue:#,0.##}";
StartPosition.Value = new Vector2(StartPosition.Value.X, y.NewValue);
}, true);
@@ -199,10 +201,9 @@ namespace osu.Game.Rulesets.Osu.Edit
StartPositionY.Value = pos.NewValue.Y;
});
Spacing.BindValueChanged(spacing =>
GridLineSpacing.BindValueChanged(spacing =>
{
spacingSlider.ContractedLabelText = $"S: {spacing.NewValue:#,0.##}";
spacingSlider.ExpandedLabelText = $"Spacing: {spacing.NewValue:#,0.##}";
SpacingVector.Value = new Vector2(spacing.NewValue);
editorBeatmap.GridSize = (int)spacing.NewValue;
}, true);
@@ -210,7 +211,6 @@ namespace osu.Game.Rulesets.Osu.Edit
GridLinesRotation.BindValueChanged(rotation =>
{
gridLinesRotationSlider.ContractedLabelText = $"R: {rotation.NewValue:#,0.##}";
gridLinesRotationSlider.ExpandedLabelText = $"Rotation: {rotation.NewValue:#,0.##}";
}, true);
GridType.BindValueChanged(v =>
@@ -239,6 +239,8 @@ namespace osu.Game.Rulesets.Osu.Edit
{
gridTypeButtons.FadeTo(v.NewValue ? 1f : 0f, 500, Easing.OutQuint);
gridTypeButtons.BypassAutoSizeAxes = !v.NewValue ? Axes.Y : Axes.None;
Spacing = v.NewValue ? new Vector2(5) : new Vector2(15);
}, true);
}
@@ -252,7 +254,7 @@ namespace osu.Game.Rulesets.Osu.Edit
switch (e.Action)
{
case GlobalAction.EditorCycleGridSpacing:
Spacing.Value = Spacing.Value * 2 >= max_automatic_spacing ? Spacing.Value / 8 : Spacing.Value * 2;
GridLineSpacing.Value = GridLineSpacing.Value * 2 >= max_automatic_spacing ? GridLineSpacing.Value / 8 : GridLineSpacing.Value * 2;
return true;
case GlobalAction.EditorCycleGridType:
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using JetBrains.Annotations;
@@ -142,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.Edit
case PositionSnapGridType.Triangle:
var triangularPositionSnapGrid = new TriangularPositionSnapGrid();
triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
triangularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.GridLineSpacing);
triangularPositionSnapGrid.GridLineRotation.BindTo(OsuGridToolboxGroup.GridLinesRotation);
positionSnapGrid = triangularPositionSnapGrid;
@@ -151,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Edit
case PositionSnapGridType.Circle:
var circularPositionSnapGrid = new CircularPositionSnapGrid();
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.Spacing);
circularPositionSnapGrid.Spacing.BindTo(OsuGridToolboxGroup.GridLineSpacing);
positionSnapGrid = circularPositionSnapGrid;
break;
@@ -171,7 +172,8 @@ namespace osu.Game.Rulesets.Osu.Edit
=> new OsuBlueprintContainer(this);
public override string ConvertSelectionToString()
=> string.Join(',', selectedHitObjects.Cast<OsuHitObject>().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString()));
=> string.Join(',', selectedHitObjects.Cast<OsuHitObject>().OrderBy(h => h.StartTime)
.Select(h => (h.IndexInCurrentCombo + 1).ToString(CultureInfo.InvariantCulture)));
// 1,2,3,4 ...
private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled);
@@ -67,6 +67,9 @@ namespace osu.Game.Rulesets.Osu.Mods
{
if (LastAcceptedAction != null && nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
LastAcceptedAction = null;
if (LastAcceptedAction != null && gameplayClock.IsRewinding)
LastAcceptedAction = null;
}
protected abstract bool CheckValidNewAction(OsuAction action);
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => @"Don't use the same key twice in a row!";
public override IconUsage? Icon => OsuIcon.ModAlternate;
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray();
public override bool Ranked => true;
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action;
}
@@ -16,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override IconUsage? Icon => OsuIcon.ModSingleTap;
public override LocalisableString Description => @"You must only use one key!";
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray();
public override bool Ranked => true;
protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action;
}
@@ -103,6 +103,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
leaderboard.Origin = Anchor.BottomLeft;
leaderboard.Position = pos;
}
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
})
{
Children = new Drawable[]
@@ -77,9 +77,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
base.LoadComplete();
RelativeSizeAxes = Axes.Both;
}
LifetimeStart = smokeStartTime = Time.Current;
public void StartDrawing(double time)
{
LifetimeStart = smokeStartTime = time;
LifetimeEnd = smokeEndTime = double.MaxValue;
SmokePoints.Clear();
lastPosition = null;
totalDistance = pointInterval;
}
+27 -5
View File
@@ -49,6 +49,18 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
/// </summary>
protected bool AllowPartRotation { get; set; }
private Vector2 cursorScale;
public Vector2 CursorScale
{
get => cursorScale;
set
{
cursorScale = value;
Invalidate(Invalidation.DrawNode);
}
}
/// <summary>
/// The trail part texture origin.
/// </summary>
@@ -233,6 +245,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private float time;
private float fadeExponent;
private float angle;
private Vector2 cursorScale;
private readonly TrailPart[] parts = new TrailPart[max_sprites];
private Vector2 originPosition;
@@ -253,6 +266,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
time = Source.time;
fadeExponent = Source.FadeExponent;
angle = Source.AllowPartRotation ? float.DegreesToRadians(Source.PartRotation) : 0;
cursorScale = Source.cursorScale;
originPosition = Vector2.Zero;
@@ -307,7 +321,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = rotateAround(
new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y),
new Vector2(
part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X * cursorScale.X,
part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.BottomLeft,
TextureRect = new Vector4(0, 0, 1, 1),
@@ -318,8 +334,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = rotateAround(
new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X,
part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y), part.Position, sin, cos),
new Vector2(
part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X * cursorScale.X,
part.Position.Y + texture.DisplayHeight * (1 - originPosition.Y) * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.BottomRight,
TextureRect = new Vector4(0, 0, 1, 1),
Colour = DrawColourInfo.Colour.BottomRight.Linear,
@@ -329,7 +347,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = rotateAround(
new Vector2(part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y),
new Vector2(
part.Position.X + texture.DisplayWidth * (1 - originPosition.X) * part.Scale.X * cursorScale.X,
part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.TopRight,
TextureRect = new Vector4(0, 0, 1, 1),
@@ -340,7 +360,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
vertexBatch.Add(new TexturedTrailVertex
{
Position = rotateAround(
new Vector2(part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X, part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y),
new Vector2(
part.Position.X - texture.DisplayWidth * originPosition.X * part.Scale.X * cursorScale.X,
part.Position.Y - texture.DisplayHeight * originPosition.Y * part.Scale.Y * cursorScale.Y),
part.Position, sin, cos),
TexturePosition = textureRect.TopLeft,
TextureRect = new Vector4(0, 0, 1, 1),
@@ -64,8 +64,14 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
var newScale = new Vector2(e.NewValue);
rippleVisualiser.CursorScale = newScale;
cursorTrail.Scale = newScale;
updateTrailScale();
}, true);
cursorTrail.OnSkinChanged += updateTrailScale;
}
private void updateTrailScale()
{
if (cursorTrail.Drawable is CursorTrail trail) trail.CursorScale = new Vector2(ActiveCursor.CursorScale.Value);
}
private int downCount;
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Performance;
using osu.Game.Graphics;
@@ -12,7 +11,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis
{
public partial class CursorPathContainer : Path
public partial class CursorPathContainer : SmoothPath
{
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
private readonly SortedSet<AnalysisFrameEntry> aliveEntries = new SortedSet<AnalysisFrameEntry>(new AimLinePointComparator());
@@ -22,14 +21,13 @@ namespace osu.Game.Rulesets.Osu.UI.ReplayAnalysis
lifetimeManager.EntryBecameAlive += entryBecameAlive;
lifetimeManager.EntryBecameDead += entryBecameDead;
PathRadius = 0.5f;
PathRadius = 1f;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Pink2;
BackgroundColour = colours.Pink2.Opacity(0);
}
protected override void Update()
+15 -6
View File
@@ -1,9 +1,9 @@
// 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.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
@@ -19,17 +19,24 @@ namespace osu.Game.Rulesets.Osu.UI
/// </summary>
public partial class SmokeContainer : Container, IRequireHighFrequencyMousePosition, IKeyBindingHandler<OsuAction>
{
private DrawablePool<SmokeSkinnableDrawable> segmentPool = null!;
private SmokeSkinnableDrawable? currentSegmentSkinnable;
private Vector2 lastMousePosition;
public override bool ReceivePositionalInputAt(Vector2 _) => true;
[BackgroundDependencyLoader]
private void load()
{
AddInternal(segmentPool = new DrawablePool<SmokeSkinnableDrawable>(10));
}
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
{
if (e.Action == OsuAction.Smoke)
{
AddInternal(currentSegmentSkinnable = new SmokeSkinnableDrawable(new OsuSkinComponentLookup(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment()));
AddInternal(currentSegmentSkinnable = segmentPool.Get(segment => segment.Segment?.StartDrawing(Time.Current)));
// Add initial position immediately.
addPosition();
@@ -59,17 +66,19 @@ namespace osu.Game.Rulesets.Osu.UI
return base.OnMouseMove(e);
}
private void addPosition() => (currentSegmentSkinnable?.Drawable as SmokeSegment)?.AddPosition(lastMousePosition, Time.Current);
private void addPosition() => currentSegmentSkinnable?.Segment?.AddPosition(lastMousePosition, Time.Current);
private partial class SmokeSkinnableDrawable : SkinnableDrawable
{
public SmokeSegment? Segment => Drawable as SmokeSegment;
public override bool RemoveWhenNotAlive => true;
public override double LifetimeStart => Drawable.LifetimeStart;
public override double LifetimeEnd => Drawable.LifetimeEnd;
public SmokeSkinnableDrawable(ISkinComponentLookup lookup, Func<ISkinComponentLookup, Drawable>? defaultImplementation = null, ConfineMode confineMode = ConfineMode.NoScaling)
: base(lookup, defaultImplementation, confineMode)
public SmokeSkinnableDrawable()
: base(new OsuSkinComponentLookup(OsuSkinComponents.CursorSmoke), _ => new DefaultSmokeSegment())
{
}
}
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
[Resolved]
private TaikoHitObjectComposer? composer { get; set; }
protected override bool IsValidForPlacement => Precision.DefinitelyBigger(spanPlacementObject.Duration, 0);
protected override bool IsValidForPlacement => base.IsValidForPlacement && (PlacementActive == PlacementState.Waiting || Precision.DefinitelyBigger(spanPlacementObject.Duration, 0));
public TaikoSpanPlacementBlueprint(HitObject hitObject)
: base(hitObject)
@@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Taiko.Edit.Checks
protected override IEnumerable<(DifficultyRating rating, double thresholdMs, string name)> GetThresholds()
{
// See lowest difficulty requirements in https://osu.ppy.sh/wiki/en/Ranking_criteria/osu%21taiko#general
yield return (DifficultyRating.Hard, new TimeSpan(0, 3, 30).TotalMilliseconds, "Muzukashii");
yield return (DifficultyRating.Insane, new TimeSpan(0, 4, 15).TotalMilliseconds, "Oni");
yield return (DifficultyRating.Expert, new TimeSpan(0, 5, 0).TotalMilliseconds, "Inner Oni");
yield return (DifficultyRating.Hard, new TimeSpan(0, 2, 30).TotalMilliseconds, "Muzukashii");
yield return (DifficultyRating.Insane, new TimeSpan(0, 3, 15).TotalMilliseconds, "Oni");
yield return (DifficultyRating.Expert, new TimeSpan(0, 4, 0).TotalMilliseconds, "Inner Oni");
}
}
}
@@ -29,6 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override IconUsage? Icon => OsuIcon.ModSingleTap;
public override LocalisableString Description => @"One key for dons, one key for kats.";
public override bool Ranked => true;
public override double ScoreMultiplier => 1.0;
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
public override ModType Type => ModType.Conversion;
@@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
public override ModType Type => ModType.Conversion;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModRandom)).ToArray();
public override bool Ranked => true;
public void ApplyToBeatmap(IBeatmap beatmap)
{
@@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private int rollingHits;
private readonly Container tickContainer;
private SkinnableDrawable headPiece;
private Color4 colourIdle;
private Color4 colourEngaged;
@@ -59,7 +60,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Content.Add(tickContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue
Depth = -1,
});
}
@@ -79,7 +80,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override void RecreatePieces()
{
if (headPiece != null)
Content.Remove(headPiece, true);
base.RecreatePieces();
Content.Add(headPiece = createHeadPiece());
updateColour();
Height = HitObject.IsStrong ? TaikoStrongableHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE;
}
@@ -122,6 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollBody),
_ => new ElongatedCirclePiece());
private SkinnableDrawable createHeadPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollHead), _ => Empty())
{
RelativeSizeAxes = Axes.Y,
Depth = -2,
};
public override bool OnPressed(KeyBindingPressEvent<TaikoAction> e) => false;
private void onNewResult(DrawableHitObject obj, JudgementResult result)
@@ -174,7 +187,23 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private void updateColour(double fadeDuration = 0)
{
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
if (fadeDuration == 0)
{
// fade duration is 0 when calling via `RecreatePieces()`.
// in this case we want to apply the colour *without* using transforms.
// using transforms may result in the application of colour being undone via `DrawableHitObject.UpdateState()` clearing transforms.
if (MainPiece.Drawable is IHasAccentColour mainPieceWithAccentColour)
mainPieceWithAccentColour.AccentColour = newColour;
if (headPiece.Drawable is IHasAccentColour headPieceWithAccentColour)
headPieceWithAccentColour.AccentColour = newColour;
}
else
{
(MainPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
(headPiece.Drawable as IHasAccentColour)?.FadeAccent(newColour, fadeDuration);
}
}
public partial class StrongNestedHit : DrawableStrongNestedHit
@@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
spectatorList.Anchor = Anchor.BottomLeft;
spectatorList.Origin = Anchor.TopLeft;
}
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
})
{
RelativeSizeAxes = Axes.Both,
@@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
spectatorList.Origin = Anchor.TopLeft;
spectatorList.Position = new Vector2(320, -280);
}
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
})
{
RelativeSizeAxes = Axes.Both,
@@ -1,7 +1,6 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
@@ -21,14 +20,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
get
{
// the reason why this calculation is so involved is that the head & tail sprites have different sizes/radii.
// therefore naively taking the SSDQs of them and making a quad out of them results in a trapezoid shape and not a box.
var headCentre = headCircle.ScreenSpaceDrawQuad.Centre;
var headCentre = (body.ScreenSpaceDrawQuad.TopLeft + body.ScreenSpaceDrawQuad.BottomLeft) / 2;
var tailCentre = (tailCircle.ScreenSpaceDrawQuad.TopLeft + tailCircle.ScreenSpaceDrawQuad.BottomLeft) / 2;
float headRadius = headCircle.ScreenSpaceDrawQuad.Height / 2;
float tailRadius = tailCircle.ScreenSpaceDrawQuad.Height / 2;
float radius = Math.Max(headRadius, tailRadius);
float radius = body.ScreenSpaceDrawQuad.Height / 2;
var rectangle = new RectangleF(headCentre.X, headCentre.Y, tailCentre.X - headCentre.X, 0).Inflate(radius);
return new Quad(rectangle.TopLeft, rectangle.TopRight, rectangle.BottomLeft, rectangle.BottomRight);
@@ -37,8 +32,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => ScreenSpaceDrawQuad.Contains(screenSpacePos);
private LegacyCirclePiece headCircle = null!;
private Sprite body = null!;
private Sprite tailCircle = null!;
@@ -66,10 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
RelativeSizeAxes = Axes.Both,
Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
},
headCircle = new LegacyCirclePiece
{
RelativeSizeAxes = Axes.Y,
},
};
AccentColour = colours.YellowDark;
@@ -101,7 +90,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
var colour = LegacyColourCompatibility.DisallowZeroAlpha(accentColour);
headCircle.AccentColour = colour;
body.Colour = colour;
tailCircle.Colour = colour;
}
@@ -79,6 +79,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
spectatorList.Origin = Anchor.TopLeft;
spectatorList.Position = pos;
}
foreach (var d in container.OfType<ISerialisableDrawable>())
d.UsesFixedAnchor = true;
})
{
new LegacyDefaultComboCounter(),
@@ -103,6 +106,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
{
switch (taikoComponent.Component)
{
case TaikoSkinComponents.DrumRollHead:
if (GetTexture("taiko-roll-middle") != null)
return new LegacyCirclePiece();
return null;
case TaikoSkinComponents.DrumRollBody:
if (GetTexture("taiko-roll-middle") != null)
return new LegacyDrumRoll();
@@ -8,6 +8,7 @@ namespace osu.Game.Rulesets.Taiko
InputDrum,
CentreHit,
RimHit,
DrumRollHead,
DrumRollBody,
DrumRollTick,
Swell,
+85 -1
View File
@@ -42,6 +42,7 @@ namespace osu.Game.Tests.Chat
sentMessages = new List<Message>();
silencedUserIds = new List<int>();
((DummyAPIAccess)API).LocalUserState.Blocks.Clear();
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
@@ -63,6 +64,10 @@ namespace osu.Game.Tests.Chat
silencedUserIds.Clear();
return true;
case GetMessagesRequest getMessages:
getMessages.TriggerSuccess(sentMessages);
return true;
case GetUpdatesRequest updatesRequest:
updatesRequest.TriggerSuccess(new GetUpdatesResponse
{
@@ -161,6 +166,85 @@ namespace osu.Game.Tests.Chat
AddUntilStep("/help command received", () => channel.Messages.Last().Content.Contains("Supported commands"));
}
[Test]
public void TestBlockedUserMessagesAreDeletedFromInitialMessageBatch()
{
Channel channel = null;
AddStep("create channel", () => channel = createChannel(1, ChannelType.Public));
AddStep("post a message from blocked user", () => sentMessages.Add(new Message
{
ChannelId = channel.Id,
Content = "i am blocked",
SenderId = 1234
}));
AddStep("mark user as blocked", () => ((DummyAPIAccess)API).LocalUserState.Blocks.Add(new APIRelation
{
TargetUser = new APIUser { Username = "blocked", Id = 1234 },
TargetID = 1234,
}));
AddStep("join channel and select it", () =>
{
channelManager.JoinChannel(channel);
channelManager.CurrentChannel.Value = channel;
});
AddAssert("channel has no messages", () => channel.Messages, () => Is.Empty);
}
[Test]
public void TestBlockedUserMessagesAreDeletedImmediatelyOnBlock()
{
Channel channel = null;
AddStep("create channel", () => channel = createChannel(1, ChannelType.Public));
AddStep("join channel and select it", () =>
{
channelManager.JoinChannel(channel);
channelManager.CurrentChannel.Value = channel;
});
AddStep("post a message from blocked user", () => sentMessages.Add(new Message
{
ChannelId = channel.Id,
Content = "i am blocked",
SenderId = 1234
}));
AddUntilStep("channel has message", () => channel.Messages, () => Is.Not.Empty);
AddStep("block user", () => ((DummyAPIAccess)API).LocalUserState.Blocks.Add(new APIRelation
{
TargetUser = new APIUser { Username = "blocked", Id = 1234 },
TargetID = 1234,
}));
AddAssert("channel has no messages", () => channel.Messages, () => Is.Empty);
}
[Test]
public void TestPrivateChannelsPurgedOnUserChange()
{
var pmChannel = createChannel(1002, ChannelType.PM);
AddStep("join a few private channels", () =>
{
channelManager.JoinChannel(createChannel(1001, ChannelType.PM));
channelManager.JoinChannel(createChannel(1003, ChannelType.Team));
channelManager.JoinChannel(pmChannel);
});
AddStep("close a PM channel", () => channelManager.LeaveChannel(pmChannel));
AddStep("switch user", () =>
{
((DummyAPIAccess.DummyLocalUserState)API.LocalUserState).User.Value = new APIUser
{
Id = 9009,
Username = "someone_else"
};
});
AddAssert("not joined to private channels of previous user",
() => !channelManager.JoinedChannels.Select(ch => ch.Id).Any(id => id >= 1001 && id <= 1003));
}
private void handlePostMessageRequest(PostMessageRequest request)
{
var message = new Message(++currentMessageId)
@@ -191,7 +275,7 @@ namespace osu.Game.Tests.Chat
}
}
private Channel createChannel(int id, ChannelType type) => new Channel(new APIUser())
private Channel createChannel(int id, ChannelType type) => new Channel(new APIUser { Id = id })
{
Id = id,
Name = $"Channel {id}",
@@ -126,6 +126,22 @@ namespace osu.Game.Tests.Gameplay
AssertBeatmapLookup(expected_sample);
}
/// <summary>
/// Tests that a hitobject which specifies a specific sample file which doesn't exist (or isn't allowed to be looked up)
/// falls back to a normal sample.
/// </summary>
[Test]
public void TestFileSampleFallsBackToNormal()
{
const string expected_sample = "normal-hitnormal";
SetupSkins(null, expected_sample);
CreateTestWithBeatmap("file-beatmap-sample.osu");
AssertUserLookup(expected_sample);
}
/// <summary>
/// Tests that a default hitobject and control point causes <see cref="TestDefaultSampleFromUserSkin"/>.
/// </summary>
@@ -738,6 +738,16 @@ namespace osu.Game.Tests.NonVisual.Filtering
new object[] { "submitted=99999", false },
new object[] { "submitted>=2012-03-05-04", false },
new object[] { "submitted>=2012/03.05-04", false },
new object[] { "created<2012", true },
new object[] { "created<2012.03", true },
new object[] { "created<2012/03/05", true },
new object[] { "created<2012-3-5", true },
new object[] { "created<0", false },
new object[] { "created=99999", false },
new object[] { "created>=2012-03-05-04", false },
new object[] { "created>=2012/03.05-04", false },
};
[Test]
@@ -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 NUnit.Framework;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
@@ -29,17 +30,17 @@ namespace osu.Game.Tests.Online.Matchmaking
new SoloScoreInfo { UserID = 3, TotalScore = 750 },
], placement_points);
Assert.AreEqual(8, state.Users[1].Points);
Assert.AreEqual(1, state.Users[1].Placement);
Assert.AreEqual(1, state.Users[1].Rounds[1].Placement);
Assert.AreEqual(8, state.Users.GetOrAdd(1).Points);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Rounds.GetOrAdd(1).Placement);
Assert.AreEqual(6, state.Users[2].Points);
Assert.AreEqual(3, state.Users[2].Placement);
Assert.AreEqual(3, state.Users[2].Rounds[1].Placement);
Assert.AreEqual(6, state.Users.GetOrAdd(2).Points);
Assert.AreEqual(3, state.Users.GetOrAdd(2).Placement);
Assert.AreEqual(3, state.Users.GetOrAdd(2).Rounds.GetOrAdd(1).Placement);
Assert.AreEqual(7, state.Users[3].Points);
Assert.AreEqual(2, state.Users[3].Placement);
Assert.AreEqual(2, state.Users[3].Rounds[1].Placement);
Assert.AreEqual(7, state.Users.GetOrAdd(3).Points);
Assert.AreEqual(2, state.Users.GetOrAdd(3).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(3).Rounds.GetOrAdd(1).Placement);
// 2 -> 1 -> 3
@@ -51,17 +52,17 @@ namespace osu.Game.Tests.Online.Matchmaking
new SoloScoreInfo { UserID = 3, TotalScore = 500 },
], placement_points);
Assert.AreEqual(15, state.Users[1].Points);
Assert.AreEqual(1, state.Users[1].Placement);
Assert.AreEqual(2, state.Users[1].Rounds[2].Placement);
Assert.AreEqual(15, state.Users.GetOrAdd(1).Points);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(1).Rounds.GetOrAdd(2).Placement);
Assert.AreEqual(14, state.Users[2].Points);
Assert.AreEqual(2, state.Users[2].Placement);
Assert.AreEqual(1, state.Users[2].Rounds[2].Placement);
Assert.AreEqual(14, state.Users.GetOrAdd(2).Points);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
Assert.AreEqual(1, state.Users.GetOrAdd(2).Rounds.GetOrAdd(2).Placement);
Assert.AreEqual(13, state.Users[3].Points);
Assert.AreEqual(3, state.Users[3].Placement);
Assert.AreEqual(3, state.Users[3].Rounds[2].Placement);
Assert.AreEqual(13, state.Users.GetOrAdd(3).Points);
Assert.AreEqual(3, state.Users.GetOrAdd(3).Placement);
Assert.AreEqual(3, state.Users.GetOrAdd(3).Rounds.GetOrAdd(2).Placement);
}
[Test]
@@ -80,21 +81,21 @@ namespace osu.Game.Tests.Online.Matchmaking
new SoloScoreInfo { UserID = 4, TotalScore = 500 },
], placement_points);
Assert.AreEqual(7, state.Users[1].Points);
Assert.AreEqual(1, state.Users[1].Placement);
Assert.AreEqual(2, state.Users[1].Rounds[1].Placement);
Assert.AreEqual(7, state.Users.GetOrAdd(1).Points);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(1).Rounds.GetOrAdd(1).Placement);
Assert.AreEqual(7, state.Users[2].Points);
Assert.AreEqual(2, state.Users[2].Placement);
Assert.AreEqual(2, state.Users[2].Rounds[1].Placement);
Assert.AreEqual(7, state.Users.GetOrAdd(2).Points);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Rounds.GetOrAdd(1).Placement);
Assert.AreEqual(5, state.Users[3].Points);
Assert.AreEqual(3, state.Users[3].Placement);
Assert.AreEqual(4, state.Users[3].Rounds[1].Placement);
Assert.AreEqual(5, state.Users.GetOrAdd(3).Points);
Assert.AreEqual(3, state.Users.GetOrAdd(3).Placement);
Assert.AreEqual(4, state.Users.GetOrAdd(3).Rounds.GetOrAdd(1).Placement);
Assert.AreEqual(5, state.Users[4].Points);
Assert.AreEqual(4, state.Users[4].Placement);
Assert.AreEqual(4, state.Users[4].Rounds[1].Placement);
Assert.AreEqual(5, state.Users.GetOrAdd(4).Points);
Assert.AreEqual(4, state.Users.GetOrAdd(4).Placement);
Assert.AreEqual(4, state.Users.GetOrAdd(4).Rounds.GetOrAdd(1).Placement);
}
[Test]
@@ -120,8 +121,8 @@ namespace osu.Game.Tests.Online.Matchmaking
new SoloScoreInfo { UserID = 2, TotalScore = 1000 },
], placement_points);
Assert.AreEqual(1, state.Users[1].Placement);
Assert.AreEqual(2, state.Users[2].Placement);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
}
[Test]
@@ -142,12 +143,40 @@ namespace osu.Game.Tests.Online.Matchmaking
new SoloScoreInfo { UserID = 5, TotalScore = 1000 },
], placement_points);
Assert.AreEqual(1, state.Users[1].Placement);
Assert.AreEqual(2, state.Users[2].Placement);
Assert.AreEqual(3, state.Users[3].Placement);
Assert.AreEqual(4, state.Users[4].Placement);
Assert.AreEqual(5, state.Users[5].Placement);
Assert.AreEqual(6, state.Users[6].Placement);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
Assert.AreEqual(3, state.Users.GetOrAdd(3).Placement);
Assert.AreEqual(4, state.Users.GetOrAdd(4).Placement);
Assert.AreEqual(5, state.Users.GetOrAdd(5).Placement);
Assert.AreEqual(6, state.Users.GetOrAdd(6).Placement);
}
[Test]
public void AbandonOrder()
{
var state = new MatchmakingRoomState();
state.AdvanceRound();
state.RecordScores(
[
new SoloScoreInfo { UserID = 1, TotalScore = 1000 },
new SoloScoreInfo { UserID = 2, TotalScore = 500 },
], placement_points);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
state.Users.GetOrAdd(1).AbandonedAt = DateTimeOffset.Now;
state.RecordScores([], placement_points);
Assert.AreEqual(2, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(1, state.Users.GetOrAdd(2).Placement);
state.Users.GetOrAdd(2).AbandonedAt = DateTimeOffset.Now - TimeSpan.FromMinutes(1);
state.RecordScores([], placement_points);
Assert.AreEqual(1, state.Users.GetOrAdd(1).Placement);
Assert.AreEqual(2, state.Users.GetOrAdd(2).Placement);
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

@@ -79,6 +79,8 @@ namespace osu.Game.Tests.Skins
"Archives/modified-argon-20250424.osk",
// Covers "Argon" unstable rate counter
"Archives/modified-argon-20250809.osk",
// Covers legacy style performance points counter
"Archives/modified-classic-20250827.osk",
// Covers "Argon" judgement counter
"Archives/modified-argon-20250308.osk",
};
@@ -266,7 +266,11 @@ namespace osu.Game.Tests.Visual.Background
FadeAccessibleResults results = null;
AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo())));
AddStep("Transition to Results", () =>
{
player.ValidForResume = false;
player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo()));
});
AddUntilStep("Wait for results is current", () => results.IsCurrentScreen());
@@ -220,10 +220,13 @@ namespace osu.Game.Tests.Visual.Components
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
if (registerAsOwner)
dependencies.CacheAs<IPreviewTrackOwner>(this);
return dependencies;
{
// Automatically handled by interface caching.
return base.CreateChildDependencies(parent);
}
return new DependencyContainer();
}
}
@@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
double lastStarRating = 0;
double lastLength = 0;
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 600 }));
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 600 }));
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
@@ -7,6 +7,7 @@ using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets;
@@ -27,7 +28,12 @@ namespace osu.Game.Tests.Visual.Editing
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new TestBeatmap(ruleset, false);
beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
return beatmap;
}
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
@@ -10,6 +10,7 @@ using NUnit.Framework;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
@@ -27,7 +28,12 @@ namespace osu.Game.Tests.Visual.Editing
{
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new TestBeatmap(ruleset, false);
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
return beatmap;
}
private TimelineBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<TimelineBlueprintContainer>().First();
@@ -80,7 +86,7 @@ namespace osu.Game.Tests.Visual.Editing
{
InputManager.Key(Key.Number1);
blueprint = this.ChildrenOfType<TimelineHitObjectBlueprint>().First();
InputManager.MoveMouseTo(blueprint);
InputManager.MoveMouseTo(blueprint, new Vector2(-1, 0));
InputManager.Click(MouseButton.Left);
});
@@ -0,0 +1,71 @@
// 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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select.Leaderboards;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Gameplay
{
public partial class TestSceneDrawableGameplayLeaderboardScore : OsuTestScene
{
private readonly APIUser user = new APIUser { Username = "user" };
private readonly BindableLong totalScore = new BindableLong();
private readonly Bindable<int?> position = new Bindable<int?>();
private readonly BindableBool quit = new BindableBool();
private readonly BindableBool expanded = new BindableBool();
public TestSceneDrawableGameplayLeaderboardScore()
{
AddSliderStep("total score", 0, 1_000_000, 500_000, s => totalScore.Value = s);
AddSliderStep("position", 1, 100, 5, s => position.Value = s);
AddToggleStep("toggle quit", q => quit.Value = q);
AddToggleStep("toggle expanded", e => expanded.Value = e);
}
private static readonly OsuColour osu_colour = new OsuColour();
private static readonly object?[][] leaderboard_variants =
{
new object?[] { false, null },
new object?[] { true, null },
new object?[] { false, osu_colour.TeamColourRed },
new object?[] { true, osu_colour.TeamColourRed },
new object?[] { false, osu_colour.TeamColourBlue },
new object?[] { true, osu_colour.TeamColourBlue },
};
[TestCaseSource(nameof(leaderboard_variants))]
public void TestVariants(bool tracked, Color4? teamColour)
{
AddStep("show", () =>
{
GameplayLeaderboardScore score = new GameplayLeaderboardScore(user, tracked, totalScore)
{
Position = { BindTarget = position },
HasQuit = { BindTarget = quit },
TeamColour = teamColour,
};
Child = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 250,
Child = new DrawableGameplayLeaderboardScore(score)
{
Expanded = { BindTarget = expanded },
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
});
}
}
}
@@ -50,6 +50,34 @@ namespace osu.Game.Tests.Visual.Gameplay
sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Texture == null)));
}
[Test]
public void TestSpriteFadeOverflowBehaviour()
{
AddStep("create sprite", () => SetContents(_ =>
{
var layer = storyboard.GetLayer("Background");
var sprite = new StoryboardSprite(lookup_name, Anchor.TopLeft, new Vector2(256, 192));
sprite.Commands.AddAlpha(Easing.None, Time.Current, Time.Current + 2000, 0, 2);
layer.Elements.Clear();
layer.Add(sprite);
return new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
storyboard.CreateDrawable()
}
};
}));
AddUntilStep("sprite reached high opacity once", () => sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Alpha > 0.8f)));
AddUntilStep("sprite reset to low opacity", () => sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Alpha < 0.2f)));
AddUntilStep("sprite reached high opacity twice", () => sprites.All(sprite => sprite.ChildrenOfType<Sprite>().All(s => s.Alpha > 0.8f)));
}
[Test]
public void TestLookupFromStoryboard()
{
@@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osu.Game.Skinning.Triangles;
using osu.Game.Tests.Gameplay;
@@ -35,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Drawable CreateDefaultImplementation() => new TrianglesPerformancePointsCounter();
protected override Drawable CreateArgonImplementation() => new ArgonPerformancePointsCounter();
protected override Drawable CreateLegacyImplementation() => Empty();
protected override Drawable CreateLegacyImplementation() => new LegacyPerformancePointsCounter();
private Bindable<JudgementResult> lastJudgementResult => (Bindable<JudgementResult>)gameplayState.LastJudgementResult;
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
@@ -378,6 +379,23 @@ namespace osu.Game.Tests.Visual.Gameplay
() => Is.EqualTo(3));
}
[Test]
public void TestCopyPasteIdempotency()
{
string state = null!;
AddStep("select everything", () => InputManager.Keys(PlatformAction.SelectAll));
AddStep("dump state", () =>
{
state = JsonConvert.SerializeObject(skinEditor.SelectedComponents.Cast<Drawable>().Select(s => s.CreateSerialisedInfo()).ToArray());
});
AddStep("copy", () => InputManager.Keys(PlatformAction.Copy));
AddStep("delete", () => InputManager.Keys(PlatformAction.Delete));
AddStep("paste", () => InputManager.Keys(PlatformAction.Paste));
AddAssert("pasted state equals dumped",
() => JsonConvert.SerializeObject(skinEditor.SelectedComponents.Cast<Drawable>().Select(s => s.CreateSerialisedInfo()).ToArray()),
() => Is.EqualTo(state));
}
private SkinnableContainer globalHUDTarget => Player.ChildrenOfType<SkinnableContainer>()
.Single(c => c.Lookup.Lookup == GlobalSkinnableContainers.MainHUDComponents && c.Lookup.Ruleset == null);
@@ -35,7 +35,9 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay();
protected override Drawable CreateArgonImplementation() => new ArgonKeyCounterDisplay();
protected override Drawable CreateDefaultImplementation() => new DefaultKeyCounterDisplay();
protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay();
}
@@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public Drawable OverlayContent => InternalChild;
public Drawable FadingContent => (OverlayContent as Container)?.Child;
public new Drawable FadingContent => (OverlayContent as Container)?.Child;
}
}
}
@@ -0,0 +1,34 @@
// 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.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
using osu.Game.Tests.Visual.Multiplayer;
namespace osu.Game.Tests.Visual.Matchmaking
{
public abstract partial class MatchmakingTestScene : MultiplayerTestScene
{
protected override Container<Drawable> Content { get; }
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
protected MatchmakingTestScene()
{
BackgroundScreenStack backgroundStack;
base.Content.AddRange(new Drawable[]
{
backgroundStack = new BackgroundScreenStack(),
Content = new Container { RelativeSizeAxes = Axes.Both }
});
backgroundStack.Push(new MatchmakingBackgroundScreen(colourProvider));
}
}
}
@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -13,15 +14,15 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestSceneBeatmapSelectGrid : OnlinePlayTestScene
public partial class TestSceneBeatmapSelectGrid : MatchmakingTestScene
{
private MultiplayerPlaylistItem[] items = null!;
private MatchmakingPlaylistItem[] items = null!;
private BeatmapSelectGrid grid = null!;
@@ -36,24 +37,44 @@ namespace osu.Game.Tests.Visual.Matchmaking
.Take(50)
.ToArray();
IEnumerable<MatchmakingPlaylistItem> playlistItems;
if (beatmaps.Length > 0)
{
items = Enumerable.Range(1, 50).Select(i => new MultiplayerPlaylistItem
playlistItems = Enumerable.Range(1, 50).Select(i =>
{
ID = i,
BeatmapID = beatmaps[i % beatmaps.Length].OnlineID,
StarRating = i / 10.0,
}).ToArray();
var beatmap = beatmaps[i % beatmaps.Length];
return new MatchmakingPlaylistItem(
new MultiplayerPlaylistItem
{
ID = i,
BeatmapID = beatmap.OnlineID,
StarRating = i / 10.0,
},
CreateAPIBeatmap(beatmap),
Array.Empty<Mod>()
);
});
}
else
{
items = Enumerable.Range(1, 50).Select(i => new MultiplayerPlaylistItem
{
ID = i,
BeatmapID = i,
StarRating = i / 10.0,
}).ToArray();
playlistItems = Enumerable.Range(1, 50).Select(i => new MatchmakingPlaylistItem(
new MultiplayerPlaylistItem
{
ID = i,
BeatmapID = i,
StarRating = i / 10.0,
},
CreateAPIBeatmap(),
Array.Empty<Mod>()
));
}
foreach (var item in playlistItems)
item.Beatmap.StarRating = item.PlaylistItem.StarRating;
items = playlistItems.ToArray();
}
public override void SetUpSteps()
@@ -70,8 +91,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
AddStep("add items", () =>
{
foreach (var item in items)
grid.AddItem(item);
grid.AddItems(items);
});
AddWaitStep("wait for panels", 3);
@@ -85,17 +105,17 @@ namespace osu.Game.Tests.Visual.Matchmaking
// test scene is weird.
});
AddStep("add selection 1", () => grid.ChildrenOfType<BeatmapSelectPanel>().First().AddUser(new APIUser
AddStep("add selection 1", () => grid.ChildrenOfType<MatchmakingSelectPanel>().First().AddUser(new APIUser
{
Id = DummyAPIAccess.DUMMY_USER_ID,
Username = "Maarvin",
}));
AddStep("add selection 2", () => grid.ChildrenOfType<BeatmapSelectPanel>().Skip(5).First().AddUser(new APIUser
AddStep("add selection 2", () => grid.ChildrenOfType<MatchmakingSelectPanel>().Skip(5).First().AddUser(new APIUser
{
Id = 2,
Username = "peppy",
}));
AddStep("add selection 3", () => grid.ChildrenOfType<BeatmapSelectPanel>().Skip(10).First().AddUser(new APIUser
AddStep("add selection 3", () => grid.ChildrenOfType<MatchmakingSelectPanel>().Skip(10).First().AddUser(new APIUser
{
Id = 1040328,
Username = "smoogipoo",
@@ -109,7 +129,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
{
var (candidateItems, finalItem) = pickRandomItems(5);
grid.RollAndDisplayFinalBeatmap(candidateItems, finalItem);
grid.RollAndDisplayFinalBeatmap(candidateItems, finalItem, finalItem);
});
}
@@ -138,7 +158,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
grid.PlayRollAnimation(finalItem, duration: 0);
Scheduler.AddDelayed(() => grid.PresentRolledBeatmap(finalItem), 500);
Scheduler.AddDelayed(() => grid.PresentRolledBeatmap(finalItem, finalItem), 500);
});
}
@@ -153,7 +173,25 @@ namespace osu.Game.Tests.Visual.Matchmaking
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
grid.PlayRollAnimation(finalItem, duration: 0);
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(finalItem), 500);
Scheduler.AddDelayed(() => grid.PresentUnanimouslyChosenBeatmap(finalItem, finalItem), 500);
});
}
[Test]
public void TestPresentRandomItem()
{
AddStep("present random item panel", () =>
{
var (candidateItems, finalItem) = pickRandomItems(4);
grid.TransferCandidatePanelsToRollContainer(candidateItems.Append(-1).ToArray(), duration: 0);
grid.ArrangeItemsForRollAnimation(duration: 0, stagger: 0);
grid.PlayRollAnimation(-1, duration: 0);
Scheduler.AddDelayed(() =>
{
grid.PresentRolledBeatmap(-1, finalItem);
}, 500);
});
}
@@ -180,7 +218,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
AddStep("display roll order", () =>
{
var panels = grid.ChildrenOfType<BeatmapSelectPanel>().ToArray();
var panels = grid.ChildrenOfType<MatchmakingSelectPanel>().ToArray();
for (int i = 0; i < panels.Length; i++)
{
@@ -197,6 +235,22 @@ namespace osu.Game.Tests.Visual.Matchmaking
});
}
[Test]
public void TestRollAnimationFinalRandom()
{
AddStep("play animation", () =>
{
(long[] candidateItems, _) = pickRandomItems(5);
candidateItems = candidateItems.Append(-1).ToArray();
long finalItem = items.First(i => !candidateItems.Contains(i.ID)).ID;
grid.RollAndDisplayFinalBeatmap(candidateItems, -1, finalItem);
});
AddWaitStep("wait for animation", 10);
}
private (long[] candidateItems, long finalItem) pickRandomItems(int count)
{
long[] candidateItems = items.Select(it => it.ID).ToArray();
@@ -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.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -9,27 +10,45 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
using osu.Game.Tests.Visual.Multiplayer;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestSceneBeatmapSelectPanel : MultiplayerTestScene
public partial class TestSceneBeatmapSelectPanel : MatchmakingTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("join room", () =>
{
var room = CreateDefaultRoom(MatchType.Matchmaking);
room.Playlist = Enumerable.Range(1, 50).Select(i => new PlaylistItem(new MultiplayerPlaylistItem
{
ID = i,
BeatmapID = 0,
StarRating = i / 10.0,
})).ToArray();
JoinRoom(room);
});
}
[Test]
public void TestBeatmapPanel()
{
BeatmapSelectPanel? panel = null;
MatchmakingSelectPanel? panel = null;
AddStep("add panel", () =>
{
Child = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Child = panel = new BeatmapSelectPanel(new MultiplayerPlaylistItem())
Child = panel = new MatchmakingSelectPanelBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), []))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -56,10 +75,54 @@ namespace osu.Game.Tests.Visual.Matchmaking
AddStep("remove peppy", () => panel!.RemoveUser(new APIUser { Id = 2 }));
AddStep("remove maarvin", () => panel!.RemoveUser(new APIUser { Id = 6411631 }));
AddToggleStep("allow selection", value =>
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
}
[Test]
public void TestRandomPanel()
{
MatchmakingSelectPanelRandom? panel = null;
AddStep("add panel", () =>
{
if (panel != null)
panel.AllowSelection = value;
Child = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Child = panel = new MatchmakingSelectPanelRandom(new MultiplayerPlaylistItem { ID = -1 })
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
});
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
AddStep("reveal beatmap", () => panel!.PresentAsChosenBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), [])));
}
[Test]
public void TestBeatmapWithMods()
{
AddStep("add panel", () =>
{
MatchmakingSelectPanel? panel;
Child = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.Both,
Child = panel = new MatchmakingSelectPanelBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), [new OsuModHardRock(), new OsuModDoubleTime()]))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
panel.AddUser(new APIUser
{
Id = 2,
Username = "peppy",
});
});
}
}
@@ -0,0 +1,49 @@
// 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.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
using osuTK;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestSceneMatchmakingChatDisplay : MatchmakingTestScene
{
private MatchmakingChatDisplay? chat;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("add chat", () =>
{
chat?.Expire();
ScreenFooter.Add(chat = new MatchmakingChatDisplay(new Room())
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Size = new Vector2(700, 130),
Margin = new MarginPadding { Bottom = 10, Right = WaveOverlayContainer.WIDTH_PADDING - OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Alpha = 0
});
});
AddStep("show footer", () => ScreenFooter.Show());
}
[Test]
public void TestAppearDisappear()
{
AddStep("appear", () => chat!.Appear());
AddWaitStep("wait for animation", 3);
AddStep("disappear", () => chat!.Disappear());
AddWaitStep("wait for animation", 3);
}
}
}
@@ -22,11 +22,11 @@ namespace osu.Game.Tests.Visual.Matchmaking
{
Value =
[
new MatchmakingPool { Id = 0, RulesetId = 0, Name = "osu!" },
new MatchmakingPool { Id = 1, RulesetId = 1, Name = "osu!taiko" },
new MatchmakingPool { Id = 2, RulesetId = 2, Name = "osu!catch" },
new MatchmakingPool { Id = 3, RulesetId = 3, Variant = 4, Name = "osu!mania (4k)" },
new MatchmakingPool { Id = 4, RulesetId = 3, Variant = 7, Name = "osu!mania (7k)" },
new MatchmakingPool { Id = 0, RulesetId = 0, Name = "Free-for-all" },
new MatchmakingPool { Id = 1, RulesetId = 1, Name = "1v1" },
new MatchmakingPool { Id = 2, RulesetId = 2, Name = "1v1" },
new MatchmakingPool { Id = 3, RulesetId = 3, Variant = 4, Name = "1v1" },
new MatchmakingPool { Id = 4, RulesetId = 3, Variant = 7, Name = "1v1" },
]
}
});
@@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
{
base.SetUpSteps();
AddStep("load screen", () => LoadScreen(new IntroScreen()));
AddStep("load screen", () => LoadScreen(new ScreenIntro()));
}
[Test]
@@ -110,6 +110,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
state.CandidateItems = beatmaps.Select(b => b.ID).ToArray();
state.CandidateItem = beatmaps[0].ID;
state.GameplayItem = beatmaps[0].ID;
}, waitTime: 35);
changeStage(MatchmakingStage.WaitingForClientsBeatmapDownload);
@@ -124,11 +125,11 @@ namespace osu.Game.Tests.Visual.Matchmaking
foreach (var user in MultiplayerClient.ServerRoom!.Users.OrderBy(_ => RNG.Next()))
{
state.Users[user.UserID].Placement = i++;
state.Users[user.UserID].Points = (8 - i) * 7;
state.Users[user.UserID].Rounds[1].Placement = 1;
state.Users[user.UserID].Rounds[1].TotalScore = 1;
state.Users[user.UserID].Rounds[1].Statistics[HitResult.LargeBonus] = 1;
state.Users.GetOrAdd(user.UserID).Placement = i++;
state.Users.GetOrAdd(user.UserID).Points = (8 - i) * 7;
state.Users.GetOrAdd(user.UserID).Rounds.GetOrAdd(1).Placement = 1;
state.Users.GetOrAdd(user.UserID).Rounds.GetOrAdd(1).TotalScore = 1;
state.Users.GetOrAdd(user.UserID).Rounds.GetOrAdd(1).Statistics[HitResult.LargeBonus] = 1;
}
});
}
@@ -3,11 +3,10 @@
using osu.Framework.Graphics;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results;
using osu.Game.Tests.Visual.Multiplayer;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestScenePanelRoomAward : MultiplayerTestScene
public partial class TestScenePanelRoomAward : MatchmakingTestScene
{
public override void SetUpSteps()
{
@@ -6,16 +6,16 @@ using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.BeatmapSelect;
using osu.Game.Tests.Visual.Multiplayer;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestScenePickScreen : MultiplayerTestScene
public partial class TestScenePickScreen : MatchmakingTestScene
{
private readonly IReadOnlyList<APIUser> users = new[]
{
@@ -104,8 +104,28 @@ namespace osu.Game.Tests.Visual.Matchmaking
long[] candidateItems = selectedItems.ToArray();
long finalItem = candidateItems[Random.Shared.Next(candidateItems.Length)];
screen.RollFinalBeatmap(candidateItems, finalItem);
screen.RollFinalBeatmap(candidateItems, finalItem, finalItem);
});
}
[Test]
public void TestExpiredBeatmapNotShown()
{
SubScreenBeatmapSelect screen = null!;
AddStep("add screen with expired items", () =>
{
MultiplayerClient.ClientRoom!.Playlist =
[
new MultiplayerPlaylistItem(items[0]) { Expired = true },
new MultiplayerPlaylistItem(items[1])
];
Child = new ScreenStack(screen = new SubScreenBeatmapSelect());
});
AddUntilStep("items displayed", () => screen.ChildrenOfType<MatchmakingSelectPanelBeatmap>().Any());
AddAssert("expired item not shown", () => screen.ChildrenOfType<MatchmakingSelectPanelBeatmap>().Count(), () => Is.EqualTo(1));
}
}
}
@@ -11,12 +11,11 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestScenePlayerPanel : MultiplayerTestScene
public partial class TestScenePlayerPanel : MatchmakingTestScene
{
private PlayerPanel panel = null!;
@@ -27,7 +26,13 @@ namespace osu.Game.Tests.Visual.Matchmaking
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
WaitForJoined();
AddStep("add panel", () => Child = panel = new PlayerPanel(new MultiplayerRoomUser(1)
AddStep("join other player to room", () => MultiplayerClient.AddUser(new APIUser
{
Id = 2,
Username = "peppy",
}));
AddStep("add panel", () => Child = panel = new PlayerPanel(new MultiplayerRoomUser(2)
{
User = new APIUser
{
@@ -85,9 +90,9 @@ namespace osu.Game.Tests.Visual.Matchmaking
UserDictionary =
{
{
1, new MatchmakingUser
2, new MatchmakingUser
{
UserId = 1,
UserId = 2,
Placement = 1,
Points = ++points
}
@@ -100,7 +105,22 @@ namespace osu.Game.Tests.Visual.Matchmaking
[Test]
public void TestJump()
{
AddStep("jump", () => MultiplayerClient.SendUserMatchRequest(1, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely());
AddStep("jump", () => MultiplayerClient.SendUserMatchRequest(2, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely());
}
[Test]
public void TestQuit()
{
AddToggleStep("toggle quit", quit => panel.HasQuit = quit);
}
[Test]
public void TestDownloadProgress()
{
AddStep("set download progress 20%", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.Downloading(0.2f)));
AddStep("set download progress 50%", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.Downloading(0.5f)));
AddStep("set download progress 90%", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.Downloading(0.9f)));
AddStep("set locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.LocallyAvailable()));
}
}
}
@@ -8,17 +8,18 @@ using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Matchmaking.Events;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
using osu.Game.Tests.Visual.Multiplayer;
using osuTK;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestScenePlayerPanelOverlay : MultiplayerTestScene
public partial class TestScenePlayerPanelOverlay : MatchmakingTestScene
{
private PlayerPanelOverlay list = null!;
@@ -118,9 +119,12 @@ namespace osu.Game.Tests.Visual.Matchmaking
});
AddUntilStep("two panels displayed", () => this.ChildrenOfType<PlayerPanel>().Count(), () => Is.EqualTo(2));
AddAssert("no panels quit", () => this.ChildrenOfType<PlayerPanel>().Count(p => p.HasQuit), () => Is.EqualTo(0));
AddStep("remove a user", () => MultiplayerClient.RemoveUser(new APIUser { Id = 1 }));
AddUntilStep("one panel displayed", () => this.ChildrenOfType<PlayerPanel>().Count(), () => Is.EqualTo(1));
AddUntilStep("one panel quit", () => this.ChildrenOfType<PlayerPanel>().Count(p => p.HasQuit), () => Is.EqualTo(1));
AddAssert("two panels still displayed", () => this.ChildrenOfType<PlayerPanel>().Count(), () => Is.EqualTo(2));
}
[Test]
@@ -150,10 +154,69 @@ namespace osu.Game.Tests.Visual.Matchmaking
MatchmakingRoomState state = new MatchmakingRoomState();
for (int i = 0; i < room.Users.Count; i++)
state.Users[room.Users[i].UserID].Placement = placements[i];
state.Users.GetOrAdd(room.Users[i].UserID).Placement = placements[i];
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
});
}
[Test]
public void InteractionSpam()
{
AddStep("join users", () =>
{
for (int i = 0; i < 7; i++)
{
MultiplayerClient.AddUser(new MultiplayerRoomUser(i)
{
User = new APIUser
{
Username = $"User {i}"
}
});
}
});
AddStep("change to grid mode", () => list.DisplayStyle = PanelDisplayStyle.Grid);
AddStep("player jump", () => { MultiplayerClient.SendUserMatchRequest(1001, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely(); });
AddStep("local jumping", () => jumpSpam(false));
AddWaitStep("wait", 25);
AddStep("group jumping spam", () => jumpSpam(true));
AddWaitStep("wait", 25);
AddStep("change to split mode", () => list.DisplayStyle = PanelDisplayStyle.Split);
AddStep("local jumping", () => jumpSpam(false));
AddWaitStep("wait", 25);
AddStep("group jumping spam", () => jumpSpam(true));
AddWaitStep("wait", 25);
AddStep("change to hidden mode", () => list.DisplayStyle = PanelDisplayStyle.Hidden);
AddStep("local jumping", () => jumpSpam(false));
AddWaitStep("wait", 25);
AddStep("group jumping spam", () => jumpSpam(true));
AddWaitStep("wait", 25);
}
private void jumpSpam(bool everyone)
{
for (int i = 0; i < 30; i++)
{
Scheduler.AddDelayed(() =>
{
MultiplayerClient.SendUserMatchRequest(1001, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely();
}, i * 150 + RNG.NextDouble(0, 140));
if (!everyone)
continue;
for (int ii = 0; ii < 7; ii++)
{
int iii = ii;
Scheduler.AddDelayed(() =>
{
MultiplayerClient.SendUserMatchRequest(iii, new MatchmakingAvatarActionRequest { Action = MatchmakingAvatarAction.Jump }).WaitSafely();
}, i * 150 + RNG.NextDouble(0, 140));
}
}
}
}
}
@@ -11,12 +11,11 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.Results;
using osu.Game.Tests.Visual.Multiplayer;
using osuTK;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestSceneResultsScreen : MultiplayerTestScene
public partial class TestSceneResultsScreen : MatchmakingTestScene
{
public override void SetUpSteps()
{
@@ -36,28 +35,28 @@ namespace osu.Game.Tests.Visual.Matchmaking
int localUserId = API.LocalUser.Value.OnlineID;
// Overall state.
state.Users[localUserId].Placement = 1;
state.Users[localUserId].Points = 8;
state.Users.GetOrAdd(localUserId).Placement = 1;
state.Users.GetOrAdd(localUserId).Points = 8;
for (int round = 1; round <= state.CurrentRound; round++)
state.Users[localUserId].Rounds[round].Placement = round;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(round).Placement = round;
// Highest score.
state.Users[localUserId].Rounds[1].TotalScore = 1000;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(1).TotalScore = 1000;
// Highest accuracy.
state.Users[localUserId].Rounds[2].Accuracy = 0.9995;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(2).Accuracy = 0.9995;
// Highest combo.
state.Users[localUserId].Rounds[3].MaxCombo = 100;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(3).MaxCombo = 100;
// Most bonus score.
state.Users[localUserId].Rounds[4].Statistics[HitResult.LargeBonus] = 50;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(4).Statistics[HitResult.LargeBonus] = 50;
// Smallest score difference.
state.Users[localUserId].Rounds[5].TotalScore = 1000;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(5).TotalScore = 1000;
// Largest score difference.
state.Users[localUserId].Rounds[6].TotalScore = 1000;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(6).TotalScore = 1000;
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
});
@@ -103,36 +102,78 @@ namespace osu.Game.Tests.Visual.Matchmaking
int localUserId = API.LocalUser.Value.OnlineID;
// Overall state.
state.Users[localUserId].Placement = 1;
state.Users[localUserId].Points = 8;
state.Users[invalid_user_id].Placement = 2;
state.Users[invalid_user_id].Points = 7;
state.Users.GetOrAdd(localUserId).Placement = 1;
state.Users.GetOrAdd(localUserId).Points = 8;
state.Users.GetOrAdd(invalid_user_id).Placement = 2;
state.Users.GetOrAdd(invalid_user_id).Points = 7;
for (int round = 1; round <= state.CurrentRound; round++)
state.Users[localUserId].Rounds[round].Placement = round;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(round).Placement = round;
// Highest score.
state.Users[localUserId].Rounds[1].TotalScore = 1000;
state.Users[invalid_user_id].Rounds[1].TotalScore = 990;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(1).TotalScore = 1000;
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(1).TotalScore = 990;
// Highest accuracy.
state.Users[localUserId].Rounds[2].Accuracy = 0.9995;
state.Users[invalid_user_id].Rounds[2].Accuracy = 0.5;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(2).Accuracy = 0.9995;
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(2).Accuracy = 0.5;
// Highest combo.
state.Users[localUserId].Rounds[3].MaxCombo = 100;
state.Users[invalid_user_id].Rounds[3].MaxCombo = 10;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(3).MaxCombo = 100;
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(3).MaxCombo = 10;
// Most bonus score.
state.Users[localUserId].Rounds[4].Statistics[HitResult.LargeBonus] = 50;
state.Users[invalid_user_id].Rounds[4].Statistics[HitResult.LargeBonus] = 25;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(4).Statistics[HitResult.LargeBonus] = 50;
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(4).Statistics[HitResult.LargeBonus] = 25;
// Smallest score difference.
state.Users[localUserId].Rounds[5].TotalScore = 1000;
state.Users[invalid_user_id].Rounds[5].TotalScore = 999;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(5).TotalScore = 1000;
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(5).TotalScore = 999;
// Largest score difference.
state.Users[localUserId].Rounds[6].TotalScore = 1000;
state.Users[invalid_user_id].Rounds[6].TotalScore = 0;
state.Users.GetOrAdd(localUserId).Rounds.GetOrAdd(6).TotalScore = 1000;
state.Users.GetOrAdd(invalid_user_id).Rounds.GetOrAdd(6).TotalScore = 0;
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
});
}
[Test]
public void TestNoUsers()
{
AddStep("show results with no users", () =>
{
var state = new MatchmakingRoomState
{
CurrentRound = 6,
Stage = MatchmakingStage.Ended
};
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
});
}
[Test]
public void TestUserWithNoScore()
{
AddStep("join another user", () => MultiplayerClient.AddUser(new MultiplayerRoomUser(2)
{
User = new APIUser
{
Id = 2,
Username = "Other user"
}
}));
AddStep("show results with no score", () =>
{
var state = new MatchmakingRoomState
{
CurrentRound = 6,
Stage = MatchmakingStage.Ended
};
state.Users.GetOrAdd(API.LocalUser.Value.OnlineID).Rounds.GetOrAdd(1).Placement = 1;
state.Users.GetOrAdd(2);
MultiplayerClient.ChangeMatchRoomState(state).WaitSafely();
});
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Framework.Utils;
@@ -14,12 +15,11 @@ using osu.Game.Online.Rooms;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match.RoundResults;
using osu.Game.Tests.Visual.Multiplayer;
using osuTK;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestSceneRoundResultsScreen : MultiplayerTestScene
public partial class TestSceneRoundResultsScreen : MatchmakingTestScene
{
public override void SetUpSteps()
{
@@ -27,8 +27,15 @@ namespace osu.Game.Tests.Visual.Matchmaking
AddStep("join room", () => JoinRoom(CreateDefaultRoom(MatchType.Matchmaking)));
WaitForJoined();
}
setupRequestHandler();
[TestCase(2)]
[TestCase(4)]
[TestCase(8)]
[TestCase(16)]
public void TestDisplayScores(int scoreCount)
{
setupRequestHandler(scoreCount);
AddStep("load screen", () =>
{
@@ -41,7 +48,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
});
}
private void setupRequestHandler()
private void setupRequestHandler(int scoreCount)
{
AddStep("setup request handler", () =>
{
@@ -72,7 +79,7 @@ namespace osu.Game.Tests.Visual.Matchmaking
case IndexPlaylistScoresRequest index:
var result = new IndexedMultiplayerScores();
for (int i = 0; i < 8; ++i)
for (int i = 0; i < scoreCount; ++i)
{
result.Scores.Add(new MultiplayerScore
{
@@ -9,11 +9,10 @@ using osu.Game.Online.Multiplayer.MatchTypes.Matchmaking;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Matchmaking.Match;
using osu.Game.Tests.Visual.Multiplayer;
namespace osu.Game.Tests.Visual.Matchmaking
{
public partial class TestSceneStageDisplay : MultiplayerTestScene
public partial class TestSceneStageDisplay : MatchmakingTestScene
{
[Cached]
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
@@ -0,0 +1,135 @@
// 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 NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneMultiplayerSkipOverlay : MultiplayerTestScene
{
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
WaitForJoined();
AddStep("add skip overlay", () =>
{
GameplayClockContainer gameplayClockContainer;
var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0)
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new MultiplayerSkipOverlay(120000)
{
RequestSkip = () => MultiplayerClient.VoteToSkipIntro().WaitSafely(),
}
},
};
gameplayClockContainer.Start();
});
AddStep("set playing state", () => MultiplayerClient.ChangeUserState(API.LocalUser.Value.OnlineID, MultiplayerUserState.Playing));
}
[Test]
public void TestSkip()
{
for (int i = 0; i < 4; i++)
{
int userId = i;
AddStep($"join user {userId}", () =>
{
MultiplayerClient.AddUser(new APIUser
{
Id = userId,
Username = $"User {userId}"
});
MultiplayerClient.ChangeUserState(userId, MultiplayerUserState.Playing);
});
}
AddStep("user 0 votes", () => MultiplayerClient.UserVoteToSkipIntro(0).WaitSafely());
AddStep("local user votes", () => this.ChildrenOfType<MultiplayerSkipOverlay.Button>().Single().TriggerClick());
AddStep("user 1 votes", () => MultiplayerClient.UserVoteToSkipIntro(1).WaitSafely());
}
[Test]
public void TestLeavingBeforeLocalVote()
{
for (int i = 0; i < 4; i++)
{
int userId = i;
AddStep($"join user {userId}", () =>
{
MultiplayerClient.AddUser(new APIUser
{
Id = userId,
Username = $"User {userId}"
});
MultiplayerClient.ChangeUserState(userId, MultiplayerUserState.Playing);
});
}
AddStep("user 0 votes", () => MultiplayerClient.UserVoteToSkipIntro(0).WaitSafely());
AddStep("user 1 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 1 }));
AddStep("user 2 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 2 }));
AddStep("user 3 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 3 }));
AddStep("user 0 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 0 }));
}
[Test]
public void TestLeavingAfterLocalVote()
{
for (int i = 0; i < 4; i++)
{
int userId = i;
AddStep($"join user {userId}", () =>
{
MultiplayerClient.AddUser(new APIUser
{
Id = userId,
Username = $"User {userId}"
});
MultiplayerClient.ChangeUserState(userId, MultiplayerUserState.Playing);
});
}
AddStep("local user votes", () => this.ChildrenOfType<MultiplayerSkipOverlay.Button>().Single().TriggerClick());
AddStep("user 0 votes", () => MultiplayerClient.UserVoteToSkipIntro(0).WaitSafely());
AddStep("user 1 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 1 }));
AddStep("user 2 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 2 }));
AddStep("user 3 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 3 }));
AddStep("user 0 leaves", () => MultiplayerClient.RemoveUser(new APIUser { Id = 0 }));
}
public partial class TestMultiplayerSkipOverlay : MultiplayerSkipOverlay
{
public TestMultiplayerSkipOverlay()
: base(120000)
{
}
}
}
}
@@ -24,6 +24,7 @@ using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics.Carousel;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Notifications.WebSocket;
@@ -33,6 +34,7 @@ using osu.Game.Overlays.BeatmapListing;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Notifications;
using osu.Game.Overlays.Toolbar;
using osu.Game.Overlays.Volume;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Configuration;
@@ -1331,6 +1333,34 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("still nothing selected", () => Game.Beatmap.IsDefault);
}
[Test]
public void TestVolumeMeterDragDoesNotDismissFocusedOverlay()
{
AddStep("show beatmap overlay", () => Game.ShowBeatmapSet(1));
AddUntilStep("beatmap overlay still visible",
() => Game.ChildrenOfType<BeatmapSetOverlay>().SingleOrDefault()?.State.Value,
() => Is.EqualTo(Visibility.Visible));
AddStep("set game volume to max", () => Game.Dependencies.Get<FrameworkConfigManager>().SetValue(FrameworkSetting.VolumeUniversal, 1d));
AddStep("move to centre", () => InputManager.MoveMouseTo(Game));
AddStep("alt-scroll down", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.ScrollVerticalBy(-1);
InputManager.ReleaseKey(Key.AltLeft);
});
AddUntilStep("wait for volume overlay to show", () => Game.ChildrenOfType<VolumeOverlay>().SingleOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("start dragging meter", () =>
{
InputManager.MoveMouseTo(Game.ChildrenOfType<VolumeMeter>().First().ChildrenOfType<OsuSpriteText>().First());
InputManager.PressButton(MouseButton.Left);
});
AddStep("drag away", () => InputManager.MoveMouseTo(Game.ChildrenOfType<VolumeMeter>().First().ChildrenOfType<OsuSpriteText>().First(), new Vector2(0, -100)));
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("beatmap overlay still visible",
() => Game.ChildrenOfType<BeatmapSetOverlay>().SingleOrDefault()?.State.Value,
() => Is.EqualTo(Visibility.Visible));
}
private Func<Player> playToResults()
{
var player = playToCompletion();
@@ -146,5 +146,165 @@ namespace osu.Game.Tests.Visual.Online
checkCount++;
}, 10);
}
[Test]
public void TestAlternatingBackgroundDoesNotChangeAtMaxHistory()
{
AddStep("fill up the channel", () =>
{
for (int i = 0; i < Channel.MAX_HISTORY; i++)
{
channel.AddNewMessages(new Message
{
ChannelId = channel.Id,
Content = $"Message {i}",
Timestamp = DateTimeOffset.Now,
Sender = new APIUser
{
Id = 3,
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
}
});
}
});
AddUntilStep($"{Channel.MAX_HISTORY} messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(Channel.MAX_HISTORY));
ChatLine? lastLine = null;
bool lastLineAlternatingBackground = false;
AddStep("grab last line", () =>
{
lastLine = drawableChannel.ChildrenOfType<ChatLine>().Last();
lastLineAlternatingBackground = lastLine.AlternatingBackground;
});
AddStep("add another message", () => channel.AddNewMessages(new Message
{
ChannelId = channel.Id,
Content = "One final message",
Timestamp = DateTimeOffset.Now,
Sender = new APIUser
{
Id = 3,
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
}
}));
AddAssert("second-last message has same background", () => lastLine!.AlternatingBackground, () => Is.EqualTo(lastLineAlternatingBackground));
}
[Test]
public void TestAlternatingBackgroundUpdatedOnRemoval()
{
AddStep("add 3 messages", () =>
{
for (int i = 0; i < 3; i++)
{
channel.AddNewMessages(new Message
{
ChannelId = channel.Id,
Content = $"Message {i}",
Timestamp = DateTimeOffset.Now,
Sender = new APIUser
{
Id = i,
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
}
});
}
});
AddUntilStep("3 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(3));
assertAlternatingBackground(0, false);
assertAlternatingBackground(1, true);
assertAlternatingBackground(2, false);
AddStep("remove middle message", () => channel.RemoveMessagesFromUser(1));
AddUntilStep("2 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(2));
assertAlternatingBackground(0, true);
assertAlternatingBackground(1, false);
void assertAlternatingBackground(int lineIndex, bool shouldBeAlternating)
=> AddAssert($"line {lineIndex} {(shouldBeAlternating ? "has" : "does not have")} alternating background",
() => drawableChannel.ChildrenOfType<ChatLine>().ElementAt(lineIndex).AlternatingBackground,
() => Is.EqualTo(shouldBeAlternating));
}
[Test]
public void TestTimestampsUpdateOnRemoval()
{
AddStep("add 3 messages", () =>
{
channel.AddNewMessages(
new Message
{
ChannelId = channel.Id,
Content = "Message 0",
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero),
Sender = new APIUser
{
Id = 0,
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
}
},
new Message
{
ChannelId = channel.Id,
Content = "Message 1",
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero).AddSeconds(1),
Sender = new APIUser
{
Id = 1,
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
}
},
new Message
{
ChannelId = channel.Id,
Content = "Message 2",
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero).AddMinutes(1),
Sender = new APIUser
{
Id = 2,
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
}
},
new Message
{
ChannelId = channel.Id,
Content = "Message 3",
Timestamp = new DateTimeOffset(2022, 11, 21, 20, 0, 0, TimeSpan.Zero).AddMinutes(1).AddSeconds(1),
Sender = new APIUser
{
Id = 3,
Username = "LocalUser " + RNG.Next(0, int.MaxValue - 100).ToString("N")
}
}
);
});
AddUntilStep("4 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(4));
assertTimestamp(0, true);
assertTimestamp(1, false);
assertTimestamp(2, true);
assertTimestamp(3, false);
AddStep("remove message 0", () => channel.RemoveMessagesFromUser(0));
AddUntilStep("3 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(3));
assertTimestamp(0, true);
assertTimestamp(1, true);
assertTimestamp(2, false);
AddStep("remove message 2", () => channel.RemoveMessagesFromUser(2));
AddUntilStep("2 messages present", () => drawableChannel.ChildrenOfType<ChatLine>().Count(), () => Is.EqualTo(2));
assertTimestamp(0, true);
assertTimestamp(1, true);
void assertTimestamp(int lineIndex, bool shouldHaveTimestamp)
=> AddAssert($"line {lineIndex} {(shouldHaveTimestamp ? "has" : "does not have")} timestamp",
() => drawableChannel.ChildrenOfType<ChatLine>().ElementAt(lineIndex).RequiresTimestamp,
() => Is.EqualTo(shouldHaveTimestamp));
}
}
}
@@ -0,0 +1,67 @@
// 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.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Tests.Visual.UserInterface;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneGlobalRankDisplay : ThemeComparisonTestScene
{
public TestSceneGlobalRankDisplay()
: base(false)
{
}
protected override Drawable CreateContent() => new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Full,
Padding = new MarginPadding(20),
Spacing = new Vector2(40),
ChildrenEnumerable = new int?[] { 64, 423, 1_453, 3_468, 8_367, 48_342, 78_432, 375_231, 897_783, null }.Select(createDisplay)
};
private GlobalRankDisplay createDisplay(int? rank) => new GlobalRankDisplay
{
UserStatistics =
{
Value = new UserStatistics
{
GlobalRank = rank,
GlobalRankPercent = rank / 1_000_000f,
Variants =
[
new UserStatistics.Variant
{
VariantType = UserStatistics.RulesetVariant.FourKey,
GlobalRank = rank / 3,
},
new UserStatistics.Variant
{
VariantType = UserStatistics.RulesetVariant.SevenKey,
GlobalRank = 2 * rank / 3,
}
]
},
},
HighestRank =
{
Value = rank == null
? null
: new APIUser.UserRankHighest
{
Rank = rank.Value / 2,
UpdatedAt = DateTimeOffset.Now.AddMonths(-3),
}
}
};
}
}
@@ -471,7 +471,7 @@ namespace osu.Game.Tests.Visual.Online
public DrawableChannel DrawableChannel => InternalChildren.OfType<DrawableChannel>().First();
public ChannelScrollContainer ScrollContainer => (ChannelScrollContainer)((Container)DrawableChannel.Child).Child;
public ChannelScrollContainer ScrollContainer => DrawableChannel.ChildrenOfType<ChannelScrollContainer>().Single();
public FillFlowContainer FillFlow => (FillFlowContainer)ScrollContainer.Child;

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