1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-27 12:42:54 +08:00

Merge remote-tracking branch 'upstream/master' into fix-remaining-identifier-names

This commit is contained in:
Joseph Madamba 2022-01-12 15:05:07 -08:00
commit b245ffefc1
163 changed files with 2545 additions and 844 deletions

View File

@ -13,3 +13,5 @@ M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.H
M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead. M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection<T>,NotificationCallbackDelegate<T>) instead.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable<T>,NotificationCallbackDelegate<T>) instead.
M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList<T>,NotificationCallbackDelegate<T>) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList<T>,NotificationCallbackDelegate<T>) instead.
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.

View File

@ -31,7 +31,7 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:** **Latest build:**
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.15+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- | | ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.

View File

@ -51,8 +51,8 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1215.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.109.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1227.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2022.111.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";

View File

@ -52,16 +52,25 @@ namespace osu.Game.Rulesets.Catch.Edit
return true; return true;
} }
public override bool HandleFlip(Direction direction) public override bool HandleFlip(Direction direction, bool flipOverOrigin)
{ {
if (SelectedItems.Count == 0)
return false;
// This could be implemented in the future if there's a requirement for it.
if (direction == Direction.Vertical)
return false;
var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems); var selectionRange = CatchHitObjectUtils.GetPositionRange(SelectedItems);
bool changed = false; bool changed = false;
EditorBeatmap.PerformOnSelection(h => EditorBeatmap.PerformOnSelection(h =>
{ {
if (h is CatchHitObject catchObject) if (h is CatchHitObject catchObject)
changed |= handleFlip(selectionRange, catchObject); changed |= handleFlip(selectionRange, catchObject, flipOverOrigin);
}); });
return changed; return changed;
} }
@ -116,7 +125,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return Math.Clamp(deltaX, lowerBound, upperBound); return Math.Clamp(deltaX, lowerBound, upperBound);
} }
private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject) private bool handleFlip(PositionRange selectionRange, CatchHitObject hitObject, bool flipOverOrigin)
{ {
switch (hitObject) switch (hitObject)
{ {
@ -124,7 +133,7 @@ namespace osu.Game.Rulesets.Catch.Edit
return false; return false;
case JuiceStream juiceStream: case JuiceStream juiceStream:
juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX); juiceStream.OriginalX = getFlippedPosition(juiceStream.OriginalX);
foreach (var point in juiceStream.Path.ControlPoints) foreach (var point in juiceStream.Path.ControlPoints)
point.Position *= new Vector2(-1, 1); point.Position *= new Vector2(-1, 1);
@ -133,9 +142,11 @@ namespace osu.Game.Rulesets.Catch.Edit
return true; return true;
default: default:
hitObject.OriginalX = selectionRange.GetFlippedPosition(hitObject.OriginalX); hitObject.OriginalX = getFlippedPosition(hitObject.OriginalX);
return true; return true;
} }
float getFlippedPosition(float original) => flipOverOrigin ? CatchPlayfield.WIDTH - original : selectionRange.GetFlippedPosition(original);
} }
} }
} }

View File

@ -14,7 +14,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue> public class ManiaBeatmapConversionTest : BeatmapConversionTest<ManiaConvertMapping, ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";

View File

@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();
public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.5); public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new ManiaHealthProcessor(drainStartTime, 0.5);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this);

View File

@ -0,0 +1,23 @@
// 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.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Scoring
{
public class ManiaHealthProcessor : DrainingHealthProcessor
{
/// <inheritdoc/>
public ManiaHealthProcessor(double drainStartTime, double drainLenience = 0)
: base(drainStartTime, drainLenience)
{
}
protected override HitResult GetSimulatedHitResult(Judgement judgement)
{
// Users are not expected to attain perfect judgements for all notes due to the tighter hit window.
return judgement.MaxResult == HitResult.Perfect ? HitResult.Great : judgement.MaxResult;
}
}
}

View File

@ -0,0 +1,225 @@
// 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.Input.Events;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public class TestSceneSliderSnapping : EditorTestScene
{
private const double beat_length = 1000;
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var controlPointInfo = new ControlPointInfo();
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
return new TestBeatmap(ruleset, false)
{
ControlPointInfo = controlPointInfo
};
}
private Slider slider;
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("add unsnapped slider", () => EditorBeatmap.Add(slider = new Slider
{
StartTime = 0,
Position = OsuPlayfield.BASE_SIZE / 5,
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(Vector2.Zero),
new PathControlPoint(OsuPlayfield.BASE_SIZE * 2 / 5),
new PathControlPoint(OsuPlayfield.BASE_SIZE * 3 / 5)
}
}
}));
AddStep("set beat divisor to 1/1", () =>
{
var beatDivisor = (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor));
beatDivisor.Value = 1;
});
}
[Test]
public void TestMovingUnsnappedSliderNodesSnaps()
{
PathControlPointPiece sliderEnd = null;
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("select slider end", () =>
{
sliderEnd = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre);
});
AddStep("move slider end", () =>
{
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre - new Vector2(0, 20));
InputManager.ReleaseButton(MouseButton.Left);
});
assertSliderSnapped(true);
}
[Test]
public void TestAddingControlPointToUnsnappedSliderNodesSnaps()
{
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to new point location", () =>
{
var firstPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
var secondPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
InputManager.MoveMouseTo((firstPiece.ScreenSpaceDrawQuad.Centre + secondPiece.ScreenSpaceDrawQuad.Centre) / 2);
});
AddStep("move slider end", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ControlLeft);
});
assertSliderSnapped(true);
}
[Test]
public void TestRemovingControlPointFromUnsnappedSliderNodesSnaps()
{
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to second control point", () =>
{
var secondPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
InputManager.MoveMouseTo(secondPiece);
});
AddStep("quick delete", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.PressButton(MouseButton.Right);
InputManager.ReleaseKey(Key.ShiftLeft);
});
assertSliderSnapped(true);
}
[Test]
public void TestResizingUnsnappedSliderSnaps()
{
SelectionBoxScaleHandle handle = null;
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to scale handle", () =>
{
handle = this.ChildrenOfType<SelectionBoxScaleHandle>().First();
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
});
AddStep("scale slider", () =>
{
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(20, 20));
InputManager.ReleaseButton(MouseButton.Left);
});
assertSliderSnapped(true);
}
[Test]
public void TestRotatingUnsnappedSliderDoesNotSnap()
{
SelectionBoxRotationHandle handle = null;
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("move mouse to rotate handle", () =>
{
handle = this.ChildrenOfType<SelectionBoxRotationHandle>().First();
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
});
AddStep("scale slider", () =>
{
InputManager.PressButton(MouseButton.Left);
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre + new Vector2(0, 20));
InputManager.ReleaseButton(MouseButton.Left);
});
assertSliderSnapped(false);
}
[Test]
public void TestFlippingSliderDoesNotSnap()
{
OsuSelectionHandler selectionHandler = null;
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("flip slider horizontally", () =>
{
selectionHandler = this.ChildrenOfType<OsuSelectionHandler>().Single();
selectionHandler.OnPressed(new KeyBindingPressEvent<GlobalAction>(InputManager.CurrentState, GlobalAction.EditorFlipHorizontally));
});
assertSliderSnapped(false);
AddStep("flip slider vertically", () =>
{
selectionHandler = this.ChildrenOfType<OsuSelectionHandler>().Single();
selectionHandler.OnPressed(new KeyBindingPressEvent<GlobalAction>(InputManager.CurrentState, GlobalAction.EditorFlipVertically));
});
assertSliderSnapped(false);
}
[Test]
public void TestReversingSliderDoesNotSnap()
{
assertSliderSnapped(false);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("reverse slider", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.ControlLeft);
});
assertSliderSnapped(false);
}
private void assertSliderSnapped(bool snapped)
=> AddAssert($"slider is {(snapped ? "" : "not ")}snapped", () =>
{
double durationInBeatLengths = slider.Duration / beat_length;
double fractionalPart = durationInBeatLengths - (int)durationInBeatLengths;
return Precision.AlmostEquals(fractionalPart, 0) == snapped;
});
}
}

View File

@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(m => m is OsuModNoFail)) if (mods.Any(m => m is OsuModNoFail))
multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount); multiplier *= Math.Max(0.90, 1.0 - 0.02 * effectiveMissCount);
if (mods.Any(m => m is OsuModSpunOut)) if (mods.Any(m => m is OsuModSpunOut) && totalHits > 0)
multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85);
if (mods.Any(h => h is OsuModRelax)) if (mods.Any(h => h is OsuModRelax))

View File

@ -283,6 +283,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
} }
} }
// Snap the path to the current beat divisor before checking length validity.
slider.SnapTo(snapProvider);
if (!slider.Path.HasValidLength) if (!slider.Path.HasValidLength)
{ {
for (int i = 0; i < slider.Path.ControlPoints.Count; i++) for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
@ -290,6 +293,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.Position = oldPosition; slider.Position = oldPosition;
slider.StartTime = oldStartTime; slider.StartTime = oldStartTime;
// Snap the path length again to undo the invalid length.
slider.SnapTo(snapProvider);
return; return;
} }

View File

@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPoints.BindTo(HitObject.Path.ControlPoints); controlPoints.BindTo(HitObject.Path.ControlPoints);
pathVersion.BindTo(HitObject.Path.Version); pathVersion.BindTo(HitObject.Path.Version);
pathVersion.BindValueChanged(_ => updatePath()); pathVersion.BindValueChanged(_ => editorBeatmap?.Update(HitObject));
BodyPiece.UpdateFrom(HitObject); BodyPiece.UpdateFrom(HitObject);
} }
@ -208,6 +208,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// Move the control points from the insertion index onwards to make room for the insertion // Move the control points from the insertion index onwards to make room for the insertion
controlPoints.Insert(insertionIndex, pathControlPoint); controlPoints.Insert(insertionIndex, pathControlPoint);
HitObject.SnapTo(composer);
return pathControlPoint; return pathControlPoint;
} }
@ -227,7 +229,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPoints.Remove(c); controlPoints.Remove(c);
} }
// If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted // Snap the slider to the current beat divisor before checking length validity.
HitObject.SnapTo(composer);
// If there are 0 or 1 remaining control points, or the slider has an invalid length, it is in a degenerate form and should be deleted
if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength) if (controlPoints.Count <= 1 || !HitObject.Path.HasValidLength)
{ {
placementHandler?.Delete(HitObject); placementHandler?.Delete(HitObject);
@ -242,12 +247,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
HitObject.Position += first; HitObject.Position += first;
} }
private void updatePath()
{
HitObject.Path.ExpectedDistance.Value = composer?.GetSnappedDistanceFromDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
editorBeatmap?.Update(HitObject);
}
private void convertToStream() private void convertToStream()
{ {
if (editorBeatmap == null || changeHandler == null || beatDivisor == null) if (editorBeatmap == null || changeHandler == null || beatDivisor == null)

View File

@ -1,15 +1,20 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable enable
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Screens.Edit.Compose.Components;
using osuTK; using osuTK;
@ -17,6 +22,9 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
public class OsuSelectionHandler : EditorSelectionHandler public class OsuSelectionHandler : EditorSelectionHandler
{ {
[Resolved(CanBeNull = true)]
private IPositionSnapProvider? positionSnapProvider { get; set; }
/// <summary> /// <summary>
/// During a transform, the initial origin is stored so it can be used throughout the operation. /// During a transform, the initial origin is stored so it can be used throughout the operation.
/// </summary> /// </summary>
@ -26,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit
/// During a transform, the initial path types of a single selected slider are stored so they /// During a transform, the initial path types of a single selected slider are stored so they
/// can be maintained throughout the operation. /// can be maintained throughout the operation.
/// </summary> /// </summary>
private List<PathType?> referencePathTypes; private List<PathType?>? referencePathTypes;
protected override void OnSelectionChanged() protected override void OnSelectionChanged()
{ {
@ -84,18 +92,28 @@ namespace osu.Game.Rulesets.Osu.Edit
return true; return true;
} }
public override bool HandleFlip(Direction direction) public override bool HandleFlip(Direction direction, bool flipOverOrigin)
{ {
var hitObjects = selectedMovableObjects; var hitObjects = selectedMovableObjects;
var selectedObjectsQuad = getSurroundingQuad(hitObjects); var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects);
bool didFlip = false;
foreach (var h in hitObjects) foreach (var h in hitObjects)
{ {
h.Position = GetFlippedPosition(direction, selectedObjectsQuad, h.Position); var flippedPosition = GetFlippedPosition(direction, flipQuad, h.Position);
if (!Precision.AlmostEquals(flippedPosition, h.Position))
{
h.Position = flippedPosition;
didFlip = true;
}
if (h is Slider slider) if (h is Slider slider)
{ {
didFlip = true;
foreach (var point in slider.Path.ControlPoints) foreach (var point in slider.Path.ControlPoints)
{ {
point.Position = new Vector2( point.Position = new Vector2(
@ -106,7 +124,7 @@ namespace osu.Game.Rulesets.Osu.Edit
} }
} }
return true; return didFlip;
} }
public override bool HandleScale(Vector2 scale, Anchor reference) public override bool HandleScale(Vector2 scale, Anchor reference)
@ -186,6 +204,10 @@ namespace osu.Game.Rulesets.Osu.Edit
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i) for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
slider.Path.ControlPoints[i].Type = referencePathTypes[i]; slider.Path.ControlPoints[i].Type = referencePathTypes[i];
// Snap the slider's length to the current beat divisor
// to calculate the final resulting duration / bounding box before the final checks.
slider.SnapTo(positionSnapProvider);
//if sliderhead or sliderend end up outside playfield, revert scaling. //if sliderhead or sliderend end up outside playfield, revert scaling.
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
(bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad);
@ -195,6 +217,9 @@ namespace osu.Game.Rulesets.Osu.Edit
foreach (var point in slider.Path.ControlPoints) foreach (var point in slider.Path.ControlPoints)
point.Position = oldControlPoints.Dequeue(); point.Position = oldControlPoints.Dequeue();
// Snap the slider's length again to undo the potentially-invalid length applied by the previous snap.
slider.SnapTo(positionSnapProvider);
} }
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)

View File

@ -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 System;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
#nullable enable
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
internal class KiaiFlashingDrawable : BeatSyncedContainer
{
private readonly Drawable flashingDrawable;
private const float flash_opacity = 0.3f;
public KiaiFlashingDrawable(Func<Drawable?> creationFunc)
{
AutoSizeAxes = Axes.Both;
Children = new[]
{
(creationFunc.Invoke() ?? Empty()).With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
}),
flashingDrawable = (creationFunc.Invoke() ?? Empty()).With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
d.Alpha = 0;
d.Blending = BlendingParameters.Additive;
})
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
if (!effectPoint.KiaiMode)
return;
flashingDrawable
.FadeTo(flash_opacity)
.Then()
.FadeOut(timingPoint.BeatLength * 0.75f);
}
}
}

View File

@ -1,61 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
internal class KiaiFlashingSprite : BeatSyncedContainer
{
private readonly Sprite mainSprite;
private readonly Sprite flashingSprite;
public Texture Texture
{
set
{
mainSprite.Texture = value;
flashingSprite.Texture = value;
}
}
private const float flash_opacity = 0.3f;
public KiaiFlashingSprite()
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
mainSprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
flashingSprite = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0,
Blending = BlendingParameters.Additive,
}
};
}
protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
{
if (!effectPoint.KiaiMode)
return;
flashingSprite
.FadeTo(flash_opacity)
.Then()
.FadeOut(timingPoint.BeatLength * 0.75f);
}
}
}

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -68,13 +69,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it. // at this point, any further texture fetches should be correctly using the priority source if the base texture was retrieved using it.
// the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist. // the flow above handles the case where a sliderendcircle.png is retrieved from the skin, but sliderendcircleoverlay.png doesn't exist.
// expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin). // expected behaviour in this scenario is not showing the overlay, rather than using hitcircleoverlay.png (potentially from the default/fall-through skin).
Texture overlayTexture = getTextureWithFallback("overlay");
InternalChildren = new[] InternalChildren = new[]
{ {
hitCircleSprite = new KiaiFlashingSprite hitCircleSprite = new KiaiFlashingDrawable(() => new Sprite { Texture = baseTexture })
{ {
Texture = baseTexture,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
@ -82,9 +81,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Child = hitCircleOverlay = new KiaiFlashingSprite Child = hitCircleOverlay = new KiaiFlashingDrawable(() => getAnimationWithFallback(@"overlay", 1000 / 2d))
{ {
Texture = overlayTexture,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
@ -126,6 +124,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
return tex ?? skin.GetTexture($"hitcircle{name}"); return tex ?? skin.GetTexture($"hitcircle{name}");
} }
Drawable getAnimationWithFallback(string name, double frameLength)
{
Drawable animation = null;
if (!string.IsNullOrEmpty(priorityLookup))
{
animation = skin.GetAnimation($"{priorityLookup}{name}", true, true, frameLength: frameLength);
if (!allowFallback)
return animation;
}
return animation ?? skin.GetAnimation($"hitcircle{name}", true, true, frameLength: frameLength);
}
} }
protected override void LoadComplete() protected override void LoadComplete()

View File

@ -12,7 +12,6 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Taiko.Tests namespace osu.Game.Rulesets.Taiko.Tests
{ {
[TestFixture] [TestFixture]
[Timeout(10000)]
public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";

View File

@ -95,7 +95,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(decodedAfterEncode, Is.Not.Null); Assert.That(decodedAfterEncode, Is.Not.Null);
Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username)); Assert.That(decodedAfterEncode.ScoreInfo.User.Username, Is.EqualTo(scoreInfo.User.Username));
Assert.That(decodedAfterEncode.ScoreInfo.BeatmapInfoID, Is.EqualTo(scoreInfo.BeatmapInfoID));
Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset)); Assert.That(decodedAfterEncode.ScoreInfo.Ruleset, Is.EqualTo(scoreInfo.Ruleset));
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore)); Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(scoreInfo.TotalScore));
Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo)); Assert.That(decodedAfterEncode.ScoreInfo.MaxCombo, Is.EqualTo(scoreInfo.MaxCombo));

View File

@ -93,7 +93,7 @@ namespace osu.Game.Tests.Beatmaps.IO
using (var stream = File.OpenRead(tempPath)) using (var stream = File.OpenRead(tempPath))
{ {
importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath))); importedSet = await manager.Import(new ImportTask(stream, Path.GetFileName(tempPath)));
ensureLoaded(osu); await ensureLoaded(osu);
} }
Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing");
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp)); var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp));
ensureLoaded(osu); await ensureLoaded(osu);
// but contents doesn't, so existing should still be used. // but contents doesn't, so existing should still be used.
Assert.IsTrue(imported.ID == importedSecondTime.Value.ID); Assert.IsTrue(imported.ID == importedSecondTime.Value.ID);
@ -225,7 +225,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp)); var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp));
ensureLoaded(osu); await ensureLoaded(osu);
// check the newly "imported" beatmap is not the original. // check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
@ -277,7 +277,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp)); var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp));
ensureLoaded(osu); await ensureLoaded(osu);
// check the newly "imported" beatmap is not the original. // check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
@ -328,7 +328,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp)); var importedSecondTime = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp));
ensureLoaded(osu); await ensureLoaded(osu);
// check the newly "imported" beatmap is not the original. // check the newly "imported" beatmap is not the original.
Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); Assert.IsTrue(imported.ID != importedSecondTime.Value.ID);
@ -637,7 +637,7 @@ namespace osu.Game.Tests.Beatmaps.IO
if (!importer.ImportAsync(temp).Wait(10000)) if (!importer.ImportAsync(temp).Wait(10000))
Assert.Fail(@"IPC took too long to send"); Assert.Fail(@"IPC took too long to send");
ensureLoaded(osu); ensureLoaded(osu).WaitSafely();
waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000);
} }
@ -659,7 +659,7 @@ namespace osu.Game.Tests.Beatmaps.IO
string temp = TestResources.GetTestBeatmapForImport(); string temp = TestResources.GetTestBeatmapForImport();
using (File.OpenRead(temp)) using (File.OpenRead(temp))
await osu.Dependencies.Get<BeatmapManager>().Import(temp); await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu); await ensureLoaded(osu);
File.Delete(temp); File.Delete(temp);
Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't");
} }
@ -698,7 +698,7 @@ namespace osu.Game.Tests.Beatmaps.IO
await osu.Dependencies.Get<BeatmapManager>().Import(temp); await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu); await ensureLoaded(osu);
} }
finally finally
{ {
@ -741,7 +741,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp)); var imported = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp));
ensureLoaded(osu); await ensureLoaded(osu);
Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder");
} }
@ -794,7 +794,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp)); var imported = await osu.Dependencies.Get<BeatmapManager>().Import(new ImportTask(temp));
ensureLoaded(osu); await ensureLoaded(osu);
Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
@ -812,7 +812,7 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
[Test] [Test]
public async Task TestUpdateBeatmapInfo() public void TestUpdateBeatmapInfo()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
@ -822,7 +822,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
string temp = TestResources.GetTestBeatmapForImport(); string temp = TestResources.GetTestBeatmapForImport();
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
osu.Dependencies.Get<BeatmapManager>().Import(temp).WaitSafely();
// Update via the beatmap, not the beatmap info, to ensure correct linking // Update via the beatmap, not the beatmap info, to ensure correct linking
BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
@ -842,7 +843,7 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
[Test] [Test]
public async Task TestUpdateBeatmapFile() public void TestUpdateBeatmapFile()
{ {
using (HeadlessGameHost host = new CleanRunHeadlessGameHost()) using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
{ {
@ -852,7 +853,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
string temp = TestResources.GetTestBeatmapForImport(); string temp = TestResources.GetTestBeatmapForImport();
await osu.Dependencies.Get<BeatmapManager>().Import(temp);
osu.Dependencies.Get<BeatmapManager>().Import(temp).WaitSafely();
BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0];
@ -976,35 +978,35 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
public static async Task<BeatmapSetInfo> LoadQuickOszIntoOsu(OsuGameBase osu) public static Task<BeatmapSetInfo> LoadQuickOszIntoOsu(OsuGameBase osu) => Task.Factory.StartNew(() =>
{ {
string temp = TestResources.GetQuickTestBeatmapForImport(); string temp = TestResources.GetQuickTestBeatmapForImport();
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely();
ensureLoaded(osu); ensureLoaded(osu).WaitSafely();
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
} }, TaskCreationOptions.LongRunning);
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) public static Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) => Task.Factory.StartNew(() =>
{ {
string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); string temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); var importedSet = manager.Import(new ImportTask(temp)).GetResultSafely();
ensureLoaded(osu); ensureLoaded(osu).WaitSafely();
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID);
} }, TaskCreationOptions.LongRunning);
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
{ {
@ -1053,7 +1055,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.AreEqual(expected, osu.Dependencies.Get<DatabaseContextFactory>().Get().FileInfo.Count(f => f.ReferenceCount == 1)); Assert.AreEqual(expected, osu.Dependencies.Get<DatabaseContextFactory>().Get().FileInfo.Count(f => f.ReferenceCount == 1));
} }
private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) private static Task ensureLoaded(OsuGameBase osu, int timeout = 60000) => Task.Factory.StartNew(() =>
{ {
IEnumerable<BeatmapSetInfo> resultSets = null; IEnumerable<BeatmapSetInfo> resultSets = null;
var store = osu.Dependencies.Get<BeatmapManager>(); var store = osu.Dependencies.Get<BeatmapManager>();
@ -1089,14 +1091,14 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(beatmap?.HitObjects.Any() == true); Assert.IsTrue(beatmap?.HitObjects.Any() == true);
beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Any() == true); Assert.IsTrue(beatmap?.HitObjects.Any() == true);
} }, TaskCreationOptions.LongRunning);
private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000) private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
{ {
Task task = Task.Run(() => Task task = Task.Factory.StartNew(() =>
{ {
while (!result()) Thread.Sleep(200); while (!result()) Thread.Sleep(200);
}); }, TaskCreationOptions.LongRunning);
Assert.IsTrue(task.Wait(timeout), failureMessage); Assert.IsTrue(task.Wait(timeout), failureMessage);
} }

View File

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -32,7 +33,7 @@ namespace osu.Game.Tests.Beatmaps
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuGameBase osu) private void load(OsuGameBase osu)
{ {
importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result; importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).GetResultSafely();
} }
[SetUpSteps] [SetUpSteps]

View File

@ -9,6 +9,21 @@ namespace osu.Game.Tests.Chat
[TestFixture] [TestFixture]
public class MessageFormatterTests public class MessageFormatterTests
{ {
private string originalWebsiteRootUrl;
[OneTimeSetUp]
public void OneTimeSetUp()
{
originalWebsiteRootUrl = MessageFormatter.WebsiteRootUrl;
MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl;
}
[Test] [Test]
public void TestBareLink() public void TestBareLink()
{ {
@ -32,8 +47,6 @@ namespace osu.Game.Tests.Chat
[TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions/123", "https://dev.ppy.sh/beatmapsets/discussions/123")] [TestCase(LinkAction.External, "https://dev.ppy.sh/beatmapsets/discussions/123", "https://dev.ppy.sh/beatmapsets/discussions/123")]
public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link) public void TestBeatmapLinks(LinkAction expectedAction, string expectedArg, string link)
{ {
MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
Message result = MessageFormatter.FormatMessage(new Message { Content = link }); Message result = MessageFormatter.FormatMessage(new Message { Content = link });
Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(result.Content, result.DisplayContent);
@ -47,7 +60,10 @@ namespace osu.Game.Tests.Chat
[Test] [Test]
public void TestMultipleComplexLinks() public void TestMultipleComplexLinks()
{ {
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" }); Message result = MessageFormatter.FormatMessage(new Message
{
Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/"
});
Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(result.Content, result.DisplayContent);
Assert.AreEqual(3, result.Links.Count); Assert.AreEqual(3, result.Links.Count);
@ -104,7 +120,7 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual("This is a Wiki Link.", result.DisplayContent); Assert.AreEqual("This is a Wiki Link.", result.DisplayContent);
Assert.AreEqual(1, result.Links.Count); Assert.AreEqual(1, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(9, result.Links[0].Length); Assert.AreEqual(9, result.Links[0].Length);
} }
@ -117,15 +133,15 @@ namespace osu.Game.Tests.Chat
Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent); Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent);
Assert.AreEqual(3, result.Links.Count); Assert.AreEqual(3, result.Links.Count);
Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki Link", result.Links[0].Url);
Assert.AreEqual(10, result.Links[0].Index); Assert.AreEqual(10, result.Links[0].Index);
Assert.AreEqual(9, result.Links[0].Length); Assert.AreEqual(9, result.Links[0].Length);
Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki:Link", result.Links[1].Url);
Assert.AreEqual(20, result.Links[1].Index); Assert.AreEqual(20, result.Links[1].Index);
Assert.AreEqual(9, result.Links[1].Length); Assert.AreEqual(9, result.Links[1].Length);
Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); Assert.AreEqual("https://dev.ppy.sh/wiki/Wiki.Link", result.Links[2].Url);
Assert.AreEqual(29, result.Links[2].Index); Assert.AreEqual(29, result.Links[2].Index);
Assert.AreEqual(9, result.Links[2].Length); Assert.AreEqual(9, result.Links[2].Length);
} }
@ -445,12 +461,15 @@ namespace osu.Game.Tests.Chat
[Test] [Test]
public void TestLinkComplex() public void TestLinkComplex()
{ {
Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" }); Message result = MessageFormatter.FormatMessage(new Message
{
Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12"
});
Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent); Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent);
Assert.AreEqual(5, result.Links.Count); Assert.AreEqual(5, result.Links.Count);
Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); Link f = result.Links.Find(l => l.Url == "https://dev.ppy.sh/wiki/wiki links");
Assert.That(f, Is.Not.Null); Assert.That(f, Is.Not.Null);
Assert.AreEqual(44, f.Index); Assert.AreEqual(44, f.Index);
Assert.AreEqual(10, f.Length); Assert.AreEqual(10, f.Length);
@ -514,8 +533,6 @@ namespace osu.Game.Tests.Chat
[TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")] [TestCase("https://dev.ppy.sh/home/changelog/lazer/2021.1012", "lazer/2021.1012")]
public void TestChangelogLinks(string link, string expectedArg) public void TestChangelogLinks(string link, string expectedArg)
{ {
MessageFormatter.WebsiteRootUrl = "dev.ppy.sh";
LinkDetails result = MessageFormatter.GetLinkDetails(link); LinkDetails result = MessageFormatter.GetLinkDetails(link);
Assert.AreEqual(LinkAction.OpenChangelog, result.Action); Assert.AreEqual(LinkAction.OpenChangelog, result.Action);

View File

@ -6,6 +6,7 @@ using System.IO;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
@ -179,7 +180,7 @@ namespace osu.Game.Tests.Collections.IO
{ {
// intentionally spin this up on a separate task to avoid disposal deadlocks. // intentionally spin this up on a separate task to avoid disposal deadlocks.
// see https://github.com/EventStore/EventStore/issues/1179 // see https://github.com/EventStore/EventStore/issues/1179
await Task.Run(() => osu.CollectionManager.Import(stream).Wait()); await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning);
} }
} }
} }

View File

@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Models; using osu.Game.Models;
@ -104,7 +105,7 @@ namespace osu.Game.Tests.Database
liveBeatmap = beatmap.ToLive(realmFactory); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null); Debug.Assert(liveBeatmap != null);
@ -115,7 +116,7 @@ namespace osu.Game.Tests.Database
Assert.IsTrue(beatmap.IsValid); Assert.IsTrue(beatmap.IsValid);
Assert.IsFalse(beatmap.Hidden); Assert.IsFalse(beatmap.Hidden);
}); });
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
}); });
} }
@ -133,7 +134,7 @@ namespace osu.Game.Tests.Database
liveBeatmap = beatmap.ToLive(realmFactory); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null); Debug.Assert(liveBeatmap != null);
@ -141,7 +142,7 @@ namespace osu.Game.Tests.Database
{ {
liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; }); liveBeatmap.PerformWrite(beatmap => { beatmap.Hidden = true; });
liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); }); liveBeatmap.PerformRead(beatmap => { Assert.IsTrue(beatmap.Hidden); });
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
}); });
} }
@ -175,7 +176,7 @@ namespace osu.Game.Tests.Database
liveBeatmap = beatmap.ToLive(realmFactory); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null); Debug.Assert(liveBeatmap != null);
@ -195,7 +196,7 @@ namespace osu.Game.Tests.Database
var __ = liveBeatmap.Value; var __ = liveBeatmap.Value;
}); });
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
}); });
} }
@ -213,7 +214,7 @@ namespace osu.Game.Tests.Database
liveBeatmap = beatmap.ToLive(realmFactory); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null); Debug.Assert(liveBeatmap != null);
@ -223,7 +224,7 @@ namespace osu.Game.Tests.Database
{ {
var unused = liveBeatmap.Value; var unused = liveBeatmap.Value;
}); });
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
}); });
} }
@ -252,7 +253,7 @@ namespace osu.Game.Tests.Database
liveBeatmap = beatmap.ToLive(realmFactory); liveBeatmap = beatmap.ToLive(realmFactory);
} }
}, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).Wait(); }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler).WaitSafely();
Debug.Assert(liveBeatmap != null); Debug.Assert(liveBeatmap != null);

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Collections; using osu.Game.Collections;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
@ -58,7 +59,7 @@ namespace osu.Game.Tests
{ {
// Beatmap must be imported before the collection manager is loaded. // Beatmap must be imported before the collection manager is loaded.
if (withBeatmap) if (withBeatmap)
BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).Wait(); BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely();
AddInternal(CollectionManager = new CollectionManager(Storage)); AddInternal(CollectionManager = new CollectionManager(Storage));
} }

View File

@ -16,7 +16,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
{ {
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
{ {
Ruleset = new RulesetInfo { OnlineID = 5 }, Ruleset = new RulesetInfo { OnlineID = 0 },
RulesetID = 0,
StarRating = 4.0d, StarRating = 4.0d,
BaseDifficulty = new BeatmapDifficulty BaseDifficulty = new BeatmapDifficulty
{ {

View File

@ -4,6 +4,7 @@
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Game.Utils; using osu.Game.Utils;
namespace osu.Game.Tests.NonVisual namespace osu.Game.Tests.NonVisual
@ -42,9 +43,9 @@ namespace osu.Game.Tests.NonVisual
await Task.WhenAll(task1.task, task2.task, task3.task); await Task.WhenAll(task1.task, task2.task, task3.task);
Assert.That(task1.task.Result, Is.EqualTo(1)); Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1));
Assert.That(task2.task.Result, Is.EqualTo(2)); Assert.That(task2.task.GetResultSafely(), Is.EqualTo(2));
Assert.That(task3.task.Result, Is.EqualTo(3)); Assert.That(task3.task.GetResultSafely(), Is.EqualTo(3));
} }
[Test] [Test]
@ -68,9 +69,9 @@ namespace osu.Game.Tests.NonVisual
// Wait on both tasks. // Wait on both tasks.
await Task.WhenAll(task1.task, task3.task); await Task.WhenAll(task1.task, task3.task);
Assert.That(task1.task.Result, Is.EqualTo(1)); Assert.That(task1.task.GetResultSafely(), Is.EqualTo(1));
Assert.That(task2.task.IsCompleted, Is.False); Assert.That(task2.task.IsCompleted, Is.False);
Assert.That(task3.task.Result, Is.EqualTo(2)); Assert.That(task3.task.GetResultSafely(), Is.EqualTo(2));
} }
[Test] [Test]

View File

@ -100,7 +100,7 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsSoftDeleting() public void TestTrackerRespectsSoftDeleting()
{ {
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
@ -114,12 +114,12 @@ namespace osu.Game.Tests.Online
public void TestTrackerRespectsChecksum() public void TestTrackerRespectsChecksum()
{ {
AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true));
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).WaitSafely());
addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable);
AddStep("import altered beatmap", () => AddStep("import altered beatmap", () =>
{ {
beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).WaitSafely();
}); });
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
@ -129,7 +129,7 @@ namespace osu.Game.Tests.Online
}); });
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait()); AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable);
} }

View File

@ -8,6 +8,7 @@ using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO; using osu.Game.IO;
@ -187,7 +188,7 @@ namespace osu.Game.Tests.Skins.IO
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
imported.Result.PerformRead(s => imported.GetResultSafely().PerformRead(s =>
{ {
Assert.IsFalse(s.Protected); Assert.IsFalse(s.Protected);
Assert.AreNotEqual(originalSkinId, s.ID); Assert.AreNotEqual(originalSkinId, s.ID);
@ -222,7 +223,7 @@ namespace osu.Game.Tests.Skins.IO
var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk")); var imported = skinManager.Import(new ImportTask(exportStream, "exported.osk"));
imported.Result.PerformRead(s => imported.GetResultSafely().PerformRead(s =>
{ {
Assert.IsFalse(s.Protected); Assert.IsFalse(s.Protected);
Assert.AreNotEqual(originalSkinId, s.ID); Assert.AreNotEqual(originalSkinId, s.ID);

View File

@ -4,6 +4,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -24,7 +25,7 @@ namespace osu.Game.Tests.Skins
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).GetResultSafely();
beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]); beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]);
beatmap.LoadTrack(); beatmap.LoadTrack();
} }

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
@ -23,7 +24,7 @@ namespace osu.Game.Tests.Skins
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result; var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).GetResultSafely();
skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo)); skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
} }

View File

@ -6,18 +6,24 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Storyboards;
using osu.Game.Storyboards.Drawables;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Background namespace osu.Game.Tests.Visual.Background
@ -129,6 +135,46 @@ namespace osu.Game.Tests.Visual.Background
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false); AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
} }
[Test]
public void TestBeatmapBackgroundWithStoryboardClockAlwaysUsesCurrentTrack()
{
BackgroundScreenBeatmap nestedScreen = null;
WorkingBeatmap originalWorking = null;
setSupporter(true);
setSourceMode(BackgroundSource.BeatmapWithStoryboard);
AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithStoryboard());
AddAssert("background changed", () => screen.CheckLastLoadChange() == true);
AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackgroundWithStoryboard));
AddStep("start music", () => MusicController.Play());
AddUntilStep("storyboard clock running", () => screen.ChildrenOfType<DrawableStoryboard>().SingleOrDefault()?.Clock.IsRunning == true);
// of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack.
AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value)));
AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen());
// we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running.
AddUntilStep("wait for top level not alive", () => !screen.IsAlive);
AddStep("stop music", () => MusicController.Stop());
AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithStoryboard());
AddStep("change beatmap back", () => Beatmap.Value = originalWorking);
AddStep("restart music", () => MusicController.Play());
AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null);
AddStep("pop screen back to top level", () => screen.MakeCurrent());
AddStep("top level screen is current", () => screen.IsCurrentScreen());
AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false);
AddUntilStep("storyboard clock running", () => screen.ChildrenOfType<DrawableStoryboard>().Single().Clock.IsRunning);
AddStep("stop music", () => MusicController.Stop());
AddStep("restore default beatmap", () => Beatmap.SetDefault());
}
[Test] [Test]
public void TestBackgroundTypeSwitch() public void TestBackgroundTypeSwitch()
{ {
@ -198,6 +244,7 @@ namespace osu.Game.Tests.Visual.Background
}); });
private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio); private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio);
private WorkingBeatmap createTestWorkingBeatmapWithStoryboard() => new TestWorkingBeatmapWithStoryboard(Audio);
private class TestBackgroundScreenDefault : BackgroundScreenDefault private class TestBackgroundScreenDefault : BackgroundScreenDefault
{ {
@ -233,6 +280,51 @@ namespace osu.Game.Tests.Visual.Background
protected override Texture GetBackground() => new Texture(1, 1); protected override Texture GetBackground() => new Texture(1, 1);
} }
private class TestWorkingBeatmapWithStoryboard : TestWorkingBeatmap
{
public TestWorkingBeatmapWithStoryboard(AudioManager audioManager)
: base(new Beatmap(), createStoryboard(), audioManager)
{
}
protected override Track GetBeatmapTrack() => new TrackVirtual(100000);
private static Storyboard createStoryboard()
{
var storyboard = new Storyboard();
storyboard.Layers.Last().Add(new TestStoryboardElement());
return storyboard;
}
private class TestStoryboardElement : IStoryboardElementWithDuration
{
public string Path => string.Empty;
public bool IsDrawable => true;
public double StartTime => double.MinValue;
public double EndTime => double.MaxValue;
public Drawable CreateDrawable() => new DrawableTestStoryboardElement();
}
private class DrawableTestStoryboardElement : OsuSpriteText
{
public override bool RemoveWhenNotAlive => false;
public DrawableTestStoryboardElement()
{
Anchor = Origin = Anchor.Centre;
Font = OsuFont.Default.With(size: 32);
Text = "(not started)";
}
protected override void Update()
{
base.Update();
Text = Time.Current.ToString("N2");
}
}
}
private void setCustomSkin() private void setCustomSkin()
{ {
// feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin.

View File

@ -7,12 +7,14 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Input.States; using osu.Framework.Input.States;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -34,7 +36,7 @@ using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Background namespace osu.Game.Tests.Visual.Background
{ {
[TestFixture] [TestFixture]
public class TestSceneUserDimBackgrounds : OsuManualInputManagerTestScene public class TestSceneUserDimBackgrounds : ScreenTestScene
{ {
private DummySongSelect songSelect; private DummySongSelect songSelect;
private TestPlayerLoader playerLoader; private TestPlayerLoader playerLoader;
@ -49,19 +51,17 @@ namespace osu.Game.Tests.Visual.Background
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(new OsuConfigManager(LocalStorage)); Dependencies.Cache(new OsuConfigManager(LocalStorage));
manager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
Beatmap.SetDefault(); Beatmap.SetDefault();
} }
[SetUp] public override void SetUpSteps()
public virtual void SetUp() => Schedule(() =>
{ {
var stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; base.SetUpSteps();
Child = stack;
stack.Push(songSelect = new DummySongSelect()); AddStep("push song select", () => Stack.Push(songSelect = new DummySongSelect()));
}); }
/// <summary> /// <summary>
/// User settings should always be ignored on song select screen. /// User settings should always be ignored on song select screen.
@ -322,7 +322,7 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White; public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White;
public bool IsUserBlurApplied() => background.CurrentBlur == new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR); public bool IsUserBlurApplied() => Precision.AlmostEquals(background.CurrentBlur, new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR), 0.1f);
public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0); public bool IsUserBlurDisabled() => background.CurrentBlur == new Vector2(0);
@ -330,9 +330,9 @@ namespace osu.Game.Tests.Visual.Background
public bool IsBackgroundVisible() => background.CurrentAlpha == 1; public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
public bool IsBackgroundBlur() => background.CurrentBlur == new Vector2(BACKGROUND_BLUR); public bool IsBackgroundBlur() => Precision.AlmostEquals(background.CurrentBlur, new Vector2(BACKGROUND_BLUR), 0.1f);
public bool CheckBackgroundBlur(Vector2 expected) => background.CurrentBlur == expected; public bool CheckBackgroundBlur(Vector2 expected) => Precision.AlmostEquals(background.CurrentBlur, expected, 0.1f);
/// <summary> /// <summary>
/// Make sure every time a screen gets pushed, the background doesn't get replaced /// Make sure every time a screen gets pushed, the background doesn't get replaced

View File

@ -4,6 +4,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Collections
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
{ {

View File

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
@ -32,7 +33,7 @@ namespace osu.Game.Tests.Visual.Editing
public override void SetUpSteps() public override void SetUpSteps()
{ {
AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result); AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely());
base.SetUpSteps(); base.SetUpSteps();
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Editing
public override void SetUpSteps() public override void SetUpSteps()
{ {
AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result); AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).GetResultSafely());
base.SetUpSteps(); base.SetUpSteps();
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Screens; using osu.Framework.Screens;
@ -36,7 +37,9 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override bool HasCustomSteps => true; protected override bool HasCustomSteps => true;
protected override TestPlayer CreatePlayer(Ruleset ruleset) => new NonImportingPlayer(false); protected override TestPlayer CreatePlayer(Ruleset ruleset) => new FakeImportingPlayer(false);
protected new FakeImportingPlayer Player => (FakeImportingPlayer)base.Player;
protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset(); protected override Ruleset CreatePlayerRuleset() => createCustomRuleset?.Invoke() ?? new OsuRuleset();
@ -54,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestNoSubmissionOnResultsWithNoToken() public void TestNoSubmissionOnResultsWithNoToken()
{ {
prepareTokenResponse(false); prepareTestAPI(false);
createPlayerTest(); createPlayerTest();
@ -74,7 +77,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestSubmissionOnResults() public void TestSubmissionOnResults()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(); createPlayerTest();
@ -93,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestSubmissionForDifferentRuleset() public void TestSubmissionForDifferentRuleset()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(createRuleset: () => new TaikoRuleset()); createPlayerTest(createRuleset: () => new TaikoRuleset());
@ -113,7 +116,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestSubmissionForConvertedBeatmap() public void TestSubmissionForConvertedBeatmap()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(createRuleset: () => new ManiaRuleset(), createBeatmap: _ => createTestBeatmap(new OsuRuleset().RulesetInfo)); createPlayerTest(createRuleset: () => new ManiaRuleset(), createBeatmap: _ => createTestBeatmap(new OsuRuleset().RulesetInfo));
@ -133,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestNoSubmissionOnExitWithNoToken() public void TestNoSubmissionOnExitWithNoToken()
{ {
prepareTokenResponse(false); prepareTestAPI(false);
createPlayerTest(); createPlayerTest();
@ -150,7 +153,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestNoSubmissionOnEmptyFail() public void TestNoSubmissionOnEmptyFail()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(true); createPlayerTest(true);
@ -165,7 +168,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestSubmissionOnFail() public void TestSubmissionOnFail()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(true); createPlayerTest(true);
@ -182,7 +185,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestNoSubmissionOnEmptyExit() public void TestNoSubmissionOnEmptyExit()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(); createPlayerTest();
@ -195,7 +198,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestSubmissionOnExit() public void TestSubmissionOnExit()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(); createPlayerTest();
@ -207,10 +210,29 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false); AddAssert("ensure failing submission", () => Player.SubmittedScore?.ScoreInfo.Passed == false);
} }
[Test]
public void TestSubmissionOnExitDuringImport()
{
prepareTestAPI(true);
createPlayerTest();
AddStep("block imports", () => Player.AllowImportCompletion.Wait());
AddUntilStep("wait for token request", () => Player.TokenCreationRequested);
addFakeHit();
AddUntilStep("wait for import to start", () => Player.ScoreImportStarted);
AddStep("exit", () => Player.Exit());
AddStep("allow import to proceed", () => Player.AllowImportCompletion.Release(1));
AddUntilStep("ensure submission", () => Player.SubmittedScore != null && Player.ImportedScore != null);
}
[Test] [Test]
public void TestNoSubmissionOnLocalBeatmap() public void TestNoSubmissionOnLocalBeatmap()
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(false, r => createPlayerTest(false, r =>
{ {
@ -231,7 +253,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestCase(10)] [TestCase(10)]
public void TestNoSubmissionOnCustomRuleset(int? rulesetId) public void TestNoSubmissionOnCustomRuleset(int? rulesetId)
{ {
prepareTokenResponse(true); prepareTestAPI(true);
createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } }); createPlayerTest(false, createRuleset: () => new OsuRuleset { RulesetInfo = { OnlineID = rulesetId ?? -1 } });
@ -253,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay
})); }));
} }
private void prepareTokenResponse(bool validToken) private void prepareTestAPI(bool validToken)
{ {
AddStep("Prepare test API", () => AddStep("Prepare test API", () =>
{ {
@ -267,6 +289,31 @@ namespace osu.Game.Tests.Visual.Gameplay
else else
tokenRequest.TriggerFailure(new APIException("something went wrong!", null)); tokenRequest.TriggerFailure(new APIException("something went wrong!", null));
return true; return true;
case SubmitSoloScoreRequest submissionRequest:
if (validToken)
{
var requestScore = submissionRequest.Score;
submissionRequest.TriggerSuccess(new MultiplayerScore
{
ID = 1234,
User = dummyAPI.LocalUser.Value,
Rank = requestScore.Rank,
TotalScore = requestScore.TotalScore,
Accuracy = requestScore.Accuracy,
MaxCombo = requestScore.MaxCombo,
Mods = requestScore.Mods,
Statistics = requestScore.Statistics,
Passed = requestScore.Passed,
EndedAt = DateTimeOffset.Now,
Position = 1
});
return true;
}
break;
} }
return false; return false;
@ -288,15 +335,26 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
} }
private class NonImportingPlayer : TestPlayer protected class FakeImportingPlayer : TestPlayer
{ {
public NonImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false) public bool ScoreImportStarted { get; set; }
public SemaphoreSlim AllowImportCompletion { get; }
public Score ImportedScore { get; private set; }
public FakeImportingPlayer(bool allowPause = true, bool showResults = true, bool pauseOnFocusLost = false)
: base(allowPause, showResults, pauseOnFocusLost) : base(allowPause, showResults, pauseOnFocusLost)
{ {
AllowImportCompletion = new SemaphoreSlim(1);
} }
protected override Task ImportScore(Score score) protected override async Task ImportScore(Score score)
{ {
ScoreImportStarted = true;
await AllowImportCompletion.WaitAsync().ConfigureAwait(false);
ImportedScore = score;
// It was discovered that Score members could sometimes be half-populated. // It was discovered that Score members could sometimes be half-populated.
// In particular, the RulesetID property could be set to 0 even on non-osu! maps. // In particular, the RulesetID property could be set to 0 even on non-osu! maps.
// We want to test that the state of that property is consistent in this test. // We want to test that the state of that property is consistent in this test.
@ -311,8 +369,7 @@ namespace osu.Game.Tests.Visual.Gameplay
// In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context, // In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context,
// RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3. // RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3.
// //
// For the above reasons, importing is disabled in this test. // For the above reasons, actual importing is disabled in this test.
return Task.CompletedTask;
} }
} }
} }

View File

@ -9,6 +9,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -135,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded);
AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).Result); AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(true)).GetResultSafely());
AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable); AddUntilStep("state is available", () => downloadButton.State.Value == DownloadState.LocallyAvailable);

View File

@ -4,6 +4,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -60,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("import beatmap", () => AddStep("import beatmap", () =>
{ {
importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely();
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1; importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1;
}); });
} }

View File

@ -53,8 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
CreateTest(null); CreateTest(null);
AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value); AddUntilStep("completion set by processor", () => Player.ScoreProcessor.HasCompleted.Value);
AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space)); AddStep("skip outro", () => InputManager.Key(osuTK.Input.Key.Space));
AddAssert("player is no longer current screen", () => !Player.IsCurrentScreen());
AddUntilStep("wait for score shown", () => Player.IsScoreShown); AddUntilStep("wait for score shown", () => Player.IsScoreShown);
AddUntilStep("time less than storyboard duration", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < currentStoryboardDuration);
} }
[Test] [Test]

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
@ -33,11 +34,11 @@ namespace osu.Game.Tests.Visual.Menus
Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null; Queue<(IWorkingBeatmap working, TrackChangeDirection changeDirection)> trackChangeQueue = null;
// ensure we have at least two beatmaps available to identify the direction the music controller navigated to. // ensure we have at least two beatmaps available to identify the direction the music controller navigated to.
AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).Wait(), 5); AddRepeatStep("import beatmap", () => Game.BeatmapManager.Import(TestResources.CreateTestBeatmapSetInfo()).WaitSafely(), 5);
AddStep("import beatmap with track", () => AddStep("import beatmap with track", () =>
{ {
var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result; var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).GetResultSafely();
Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First()); Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First());
}); });

View File

@ -5,6 +5,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -56,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("import beatmap", () => AddStep("import beatmap", () =>
{ {
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); InitialBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0); OtherBeatmap = importedSet.Beatmaps.Last(b => b.RulesetID == 0);

View File

@ -1,13 +1,25 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable enable
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.Play;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer namespace osu.Game.Tests.Visual.Multiplayer
@ -83,16 +95,60 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
} }
private void addItem(Func<BeatmapInfo> beatmap) [Test]
public void TestCorrectRulesetSelectedAfterNewItemAdded()
{ {
addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo);
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle);
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded);
AddAssert("ruleset is correct", () => ((Player)CurrentScreen).Ruleset.Value.Equals(new OsuRuleset().RulesetInfo));
AddStep("exit player", () => CurrentScreen.Exit());
}
[Test]
public void TestCorrectModsSelectedAfterNewItemAdded()
{
addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() });
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle);
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready);
ClickButtonWhenEnabled<MultiplayerReadyButton>();
AddUntilStep("wait for player", () => CurrentScreen is Player player && player.IsLoaded);
AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any());
AddStep("exit player", () => CurrentScreen.Exit());
}
private void addItem(Func<BeatmapInfo> beatmap, RulesetInfo? ruleset = null, IReadOnlyList<Mod>? mods = null)
{
Screens.Select.SongSelect? songSelect = null;
AddStep("click add button", () => AddStep("click add button", () =>
{ {
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single()); InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null);
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded);
if (ruleset != null)
AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
if (mods != null)
AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods);
AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
} }
} }

View File

@ -8,6 +8,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -153,7 +154,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait()); AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).WaitSafely());
createPlaylistWithBeatmaps(beatmap); createPlaylistWithBeatmaps(beatmap);

View File

@ -50,6 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
AddUntilStep("wait for load", () => leaderboard.IsLoaded); AddUntilStep("wait for load", () => leaderboard.IsLoaded);
AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().Count() == 2);
AddStep("add clock sources", () => AddStep("add clock sources", () =>
{ {

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -46,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).GetResultSafely();
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
importedBeatmapId = importedBeatmap.OnlineID ?? -1; importedBeatmapId = importedBeatmap.OnlineID ?? -1;
} }

View File

@ -7,6 +7,8 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
@ -22,6 +24,8 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
@ -67,7 +71,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("import beatmap", () => AddStep("import beatmap", () =>
{ {
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
}); });
@ -438,6 +442,84 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID); AddAssert("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == client.Room?.Playlist.First().BeatmapID);
} }
[Test]
public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect()
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
});
pressReadyButton();
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID);
AddStep("Switch ruleset", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Ruleset.Value = new CatchRuleset().RulesetInfo);
AddUntilStep("Ruleset doesn't match current item", () => Ruleset.Value.OnlineID != client.Room?.Playlist.First().RulesetID);
AddStep("start match externally", () => client.StartMatch());
AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player);
AddAssert("Ruleset matches current item", () => Ruleset.Value.OnlineID == client.Room?.Playlist.First().RulesetID);
}
[Test]
public void TestPlayStartsWithCorrectModsWhileAtSongSelect()
{
createRoom(() => new Room
{
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo },
}
}
});
pressReadyButton();
AddStep("Enter song select", () =>
{
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
AddStep("Switch required mods", () => ((MultiplayerMatchSongSelect)multiplayerComponents.MultiplayerScreen.CurrentSubScreen).Mods.Value = new Mod[] { new OsuModDoubleTime() });
AddAssert("Mods don't match current item", () => !SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
AddStep("start match externally", () => client.StartMatch());
AddUntilStep("play started", () => multiplayerComponents.CurrentScreen is Player);
AddAssert("Mods match current item", () => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(client.Room.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
}
[Test] [Test]
public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap() public void TestLocalPlayDoesNotStartWhileSpectatingWithNoBeatmap()
{ {
@ -505,7 +587,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("restore beatmap", () => AddStep("restore beatmap", () =>
{ {
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
}); });

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
base.SetUpSteps(); base.SetUpSteps();
AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely());
AddStep("create leaderboard", () => AddStep("create leaderboard", () =>
{ {
@ -90,7 +91,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
public void TestUserQuit() public void TestUserQuit()
{ {
foreach (int user in users) foreach (int user in users)
AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).Result.AsNonNull())); AddStep($"mark user {user} quit", () => Client.RemoveUser(LookupCache.GetUserAsync(user).GetResultSafely().AsNonNull()));
} }
[Test] [Test]

View File

@ -4,6 +4,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
base.SetUpSteps(); base.SetUpSteps();
AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).Result); AddStep("set local user", () => ((DummyAPIAccess)API).LocalUser.Value = LookupCache.GetUserAsync(1).GetResultSafely());
AddStep("create leaderboard", () => AddStep("create leaderboard", () =>
{ {

View File

@ -83,7 +83,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
beatmapSetInfo.Beatmaps.Add(beatmap); beatmapSetInfo.Beatmaps.Add(beatmap);
} }
manager.Import(beatmapSetInfo).Wait(); manager.Import(beatmapSetInfo).WaitSafely();
} }
public override void SetUpSteps() public override void SetUpSteps()

View File

@ -5,6 +5,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -39,7 +40,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
} }

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
AddStep("import beatmap", () => AddStep("import beatmap", () =>
{ {
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
}); });

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -58,7 +59,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("import beatmap", () => AddStep("import beatmap", () =>
{ {
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
}); });

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -41,7 +42,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
} }
[SetUp] [SetUp]

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Platform; using osu.Framework.Platform;
@ -42,7 +43,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default));
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
} }
[SetUp] [SetUp]

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Utils; using osu.Framework.Utils;
@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); var beatmapSet = TestResources.CreateTestBeatmapSetInfo();
manager.Import(beatmapSet).Wait(); manager.Import(beatmapSet).WaitSafely();
} }
public override void SetUpSteps() public override void SetUpSteps()

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -51,7 +52,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("import beatmap", () => AddStep("import beatmap", () =>
{ {
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First();
}); });

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
@ -82,7 +83,7 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
AddStep("press enter", () => InputManager.Key(Key.Enter)); AddStep("press enter", () => InputManager.Key(Key.Enter));

View File

@ -4,6 +4,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -172,7 +173,7 @@ namespace osu.Game.Tests.Visual.Navigation
private void importAndWaitForSongSelect() private void importAndWaitForSongSelect()
{ {
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely());
PushAndConfirm(() => new TestPlaySongSelect()); PushAndConfirm(() => new TestPlaySongSelect());
AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526); AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526);
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions; using osu.Game.Extensions;
@ -126,7 +127,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
}, },
} }
}).Result.Value; }).GetResultSafely().Value;
}); });
AddAssert($"import {i} succeeded", () => imported != null); AddAssert($"import {i} succeeded", () => imported != null);

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation
Ruleset = new OsuRuleset().RulesetInfo Ruleset = new OsuRuleset().RulesetInfo
}, },
} }
}).Result.Value; }).GetResultSafely().Value;
}); });
} }
@ -131,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation
OnlineID = i, OnlineID = i,
BeatmapInfo = beatmap.Beatmaps.First(), BeatmapInfo = beatmap.Beatmaps.First(),
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
}).Result.Value; }).GetResultSafely().Value;
}); });
AddAssert($"import {i} succeeded", () => imported != null); AddAssert($"import {i} succeeded", () => imported != null);

View File

@ -4,6 +4,7 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -70,7 +71,7 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => songSelect = new TestPlaySongSelect()); PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
@ -104,7 +105,7 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => songSelect = new TestPlaySongSelect()); PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
@ -138,7 +139,7 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => songSelect = new TestPlaySongSelect()); PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);

View File

@ -107,19 +107,31 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden); AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
} }
[Test]
public void TestCorrectOldContentExpiration()
{
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray()));
assertAllCardsOfType<BeatmapCardNormal>(100);
AddStep("show more results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 30).ToArray()));
assertAllCardsOfType<BeatmapCardNormal>(30);
}
[Test] [Test]
public void TestCardSizeSwitching() public void TestCardSizeSwitching()
{ {
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray())); AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateAPIBeatmapSet(Ruleset.Value), 100).ToArray()));
assertAllCardsOfType<BeatmapCardNormal>(); assertAllCardsOfType<BeatmapCardNormal>(100);
setCardSize(BeatmapCardSize.Extra); setCardSize(BeatmapCardSize.Extra);
assertAllCardsOfType<BeatmapCardExtra>(); assertAllCardsOfType<BeatmapCardExtra>(100);
setCardSize(BeatmapCardSize.Normal); setCardSize(BeatmapCardSize.Normal);
assertAllCardsOfType<BeatmapCardNormal>(); assertAllCardsOfType<BeatmapCardNormal>(100);
AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("fetch for 0 beatmaps", () => fetchFor());
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
@ -323,13 +335,12 @@ namespace osu.Game.Tests.Visual.Online
private void setCardSize(BeatmapCardSize cardSize) => AddStep($"set card size to {cardSize}", () => overlay.ChildrenOfType<BeatmapListingCardSizeTabControl>().Single().Current.Value = cardSize); private void setCardSize(BeatmapCardSize cardSize) => AddStep($"set card size to {cardSize}", () => overlay.ChildrenOfType<BeatmapListingCardSizeTabControl>().Single().Current.Value = cardSize);
private void assertAllCardsOfType<T>() private void assertAllCardsOfType<T>(int expectedCount)
where T : BeatmapCard => where T : BeatmapCard =>
AddUntilStep($"all loaded beatmap cards are {typeof(T)}", () => AddUntilStep($"all loaded beatmap cards are {typeof(T)}", () =>
{ {
int loadedCorrectCount = this.ChildrenOfType<BeatmapCard>().Count(card => card.IsLoaded && card.GetType() == typeof(T)); int loadedCorrectCount = this.ChildrenOfType<BeatmapCard>().Count(card => card.IsLoaded && card.GetType() == typeof(T));
int totalCount = this.ChildrenOfType<BeatmapCard>().Count(); return loadedCorrectCount > 0 && loadedCorrectCount == expectedCount;
return loadedCorrectCount > 0 && loadedCorrectCount == totalCount;
}); });
} }
} }

View File

@ -50,63 +50,24 @@ namespace osu.Game.Tests.Visual.Online
Dependencies.Cache(new ChatOverlay()); Dependencies.Cache(new ChatOverlay());
Dependencies.Cache(dialogOverlay); Dependencies.Cache(dialogOverlay);
testLinksGeneral();
testEcho();
} }
private void clear() => AddStep("clear messages", textContainer.Clear); [SetUp]
public void Setup() => Schedule(() =>
private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions)
{ {
int index = textContainer.Count + 1; textContainer.Clear();
var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); });
textContainer.Add(newLine);
AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); [Test]
AddAssert($"msg #{index} has the right action", hasExpectedActions); public void TestLinksGeneral()
//AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic());
AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks);
bool hasExpectedActions()
{
var expectedActionsList = expectedActions.ToList();
if (expectedActionsList.Count != newLine.Message.Links.Count)
return false;
for (int i = 0; i < newLine.Message.Links.Count; i++)
{
var action = newLine.Message.Links[i].Action;
if (action != expectedActions[i]) return false;
}
return true;
}
//bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast<OsuSpriteText>().All(sprite => sprite.Font.Italics);
bool isShowingLinks()
{
bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour);
Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White;
var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList();
var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts);
return linkSprites.All(d => d.Colour == linkColour)
&& newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour);
}
}
private void testLinksGeneral()
{ {
int messageIndex = 0;
addMessageWithChecks("test!"); addMessageWithChecks("test!");
addMessageWithChecks("dev.ppy.sh!"); addMessageWithChecks("dev.ppy.sh!");
addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External); addMessageWithChecks("https://dev.ppy.sh!", 1, expectedActions: LinkAction.External);
addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp);
addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.OpenWiki);
addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); addMessageWithChecks("(osu forums)[https://dev.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[https://dev.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[osu forums](https://dev.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External);
@ -117,7 +78,8 @@ namespace osu.Game.Tests.Visual.Online
expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External });
addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External); addMessageWithChecks("[https://dev.ppy.sh/home New link format with escaped [and \\[ paired] braces]", 1, expectedActions: LinkAction.External);
addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[Markdown link format with escaped [and \\[ paired] braces](https://dev.ppy.sh/home)", 1, expectedActions: LinkAction.External);
addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2, expectedActions: new[] { LinkAction.External, LinkAction.External }); addMessageWithChecks("(Old link format with escaped (and \\( paired) parentheses)[https://dev.ppy.sh/home] and [[also a rogue wiki link]]", 2,
expectedActions: new[] { LinkAction.External, LinkAction.OpenWiki });
// note that there's 0 links here (they get removed if a channel is not found) // note that there's 0 links here (they get removed if a channel is not found)
addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).");
addMessageWithChecks("I am important!", 0, false, true); addMessageWithChecks("I am important!", 0, false, true);
@ -129,11 +91,60 @@ namespace osu.Game.Tests.Visual.Online
addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel);
addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel });
addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel);
void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions)
{
ChatLine newLine = null;
int index = messageIndex++;
AddStep("add message", () =>
{
newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index));
textContainer.Add(newLine);
});
AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount);
AddAssert($"msg #{index} has the right action", hasExpectedActions);
//AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic());
AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks);
bool hasExpectedActions()
{
var expectedActionsList = expectedActions.ToList();
if (expectedActionsList.Count != newLine.Message.Links.Count)
return false;
for (int i = 0; i < newLine.Message.Links.Count; i++)
{
var action = newLine.Message.Links[i].Action;
if (action != expectedActions[i]) return false;
}
return true;
}
//bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast<OsuSpriteText>().All(sprite => sprite.Font.Italics);
bool isShowingLinks()
{
bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour);
Color4 textColour = isAction && hasBackground ? Color4Extensions.FromHex(newLine.Message.Sender.Colour) : Color4.White;
var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList();
var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts);
return linkSprites.All(d => d.Colour == linkColour)
&& newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour);
}
}
} }
private void testEcho() [Test]
public void TestEcho()
{ {
int echoCounter = 0; int messageIndex = 0;
addEchoWithWait("sent!", "received!"); addEchoWithWait("sent!", "received!");
addEchoWithWait("https://dev.ppy.sh/home", null, 500); addEchoWithWait("https://dev.ppy.sh/home", null, 500);
@ -142,15 +153,16 @@ namespace osu.Game.Tests.Visual.Online
void addEchoWithWait(string text, string completeText = null, double delay = 250) void addEchoWithWait(string text, string completeText = null, double delay = 250)
{ {
var newLine = new ChatLine(new DummyEchoMessage(text)); int index = messageIndex++;
AddStep($"send msg #{++echoCounter} after {delay}ms", () => AddStep($"send msg #{index} after {delay}ms", () =>
{ {
ChatLine newLine = new ChatLine(new DummyEchoMessage(text));
textContainer.Add(newLine); textContainer.Add(newLine);
Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay);
}); });
AddUntilStep($"wait for msg #{echoCounter}", () => textContainer.All(line => line.Message is DummyMessage)); AddUntilStep($"wait for msg #{index}", () => textContainer.All(line => line.Message is DummyMessage));
} }
} }

View File

@ -1,38 +1,27 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Rankings.Tables; using osu.Game.Overlays.Rankings.Tables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using System.Threading; using System.Threading;
using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Catch;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Rankings; using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
public class TestSceneRankingsTables : OsuTestScene public class TestSceneRankingsTables : OsuTestScene
{ {
protected override bool UseOnlineAPI => true;
[Resolved]
private IAPIProvider api { get; set; }
[Cached] [Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
private readonly BasicScrollContainer scrollFlow; private readonly BasicScrollContainer scrollFlow;
private readonly LoadingLayer loading; private readonly LoadingLayer loading;
private CancellationTokenSource cancellationToken; private CancellationTokenSource cancellationToken;
private APIRequest request;
public TestSceneRankingsTables() public TestSceneRankingsTables()
{ {
@ -53,73 +42,120 @@ namespace osu.Game.Tests.Visual.Online
{ {
base.LoadComplete(); base.LoadComplete();
AddStep("Osu performance", () => createPerformanceTable(new OsuRuleset().RulesetInfo, null)); AddStep("User performance", createPerformanceTable);
AddStep("Mania scores", () => createScoreTable(new ManiaRuleset().RulesetInfo)); AddStep("User scores", createScoreTable);
AddStep("Taiko country scores", () => createCountryTable(new TaikoRuleset().RulesetInfo)); AddStep("Country scores", createCountryTable);
AddStep("Catch US performance page 10", () => createPerformanceTable(new CatchRuleset().RulesetInfo, "US", 10));
AddStep("Osu spotlight table (chart 271)", () => createSpotlightTable(new OsuRuleset().RulesetInfo, 271));
} }
private void createCountryTable(RulesetInfo ruleset, int page = 1) private void createCountryTable()
{ {
onLoadStarted(); onLoadStarted();
request = new GetCountryRankingsRequest(ruleset, page); var countries = new List<CountryStatistics>
((GetCountryRankingsRequest)request).Success += rankings => Schedule(() =>
{ {
var table = new CountriesTable(page, rankings.Countries); new CountryStatistics
loadTable(table); {
}); Country = new Country { FlagName = "US", FullName = "United States" },
FlagName = "US",
ActiveUsers = 2_972_623,
PlayCount = 3_086_515_743,
RankedScore = 449_407_643_332_546,
Performance = 371_974_024
},
new CountryStatistics
{
Country = new Country { FlagName = "RU", FullName = "Russian Federation" },
FlagName = "RU",
ActiveUsers = 1_609_989,
PlayCount = 1_637_052_841,
RankedScore = 221_660_827_473_004,
Performance = 163_426_476
}
};
api.Queue(request); var table = new CountriesTable(1, countries);
loadTable(table);
} }
private void createPerformanceTable(RulesetInfo ruleset, string country, int page = 1) private static List<UserStatistics> createUserStatistics() => new List<UserStatistics>
{
new UserStatistics
{
User = new APIUser
{
Username = "first active user",
Country = new Country { FlagName = "JP" },
Active = true,
},
Accuracy = 0.9972,
PlayCount = 233_215,
TotalScore = 983_231_234_656,
RankedScore = 593_231_345_897,
PP = 23_934,
GradesCount = new UserStatistics.Grades
{
SS = 35_132,
S = 23_345,
A = 12_234
}
},
new UserStatistics
{
User = new APIUser
{
Username = "inactive user",
Country = new Country { FlagName = "AU" },
Active = false,
},
Accuracy = 0.9831,
PlayCount = 195_342,
TotalScore = 683_231_234_656,
RankedScore = 393_231_345_897,
PP = 20_934,
GradesCount = new UserStatistics.Grades
{
SS = 32_132,
S = 20_345,
A = 9_234
}
},
new UserStatistics
{
User = new APIUser
{
Username = "second active user",
Country = new Country { FlagName = "PL" },
Active = true,
},
Accuracy = 0.9584,
PlayCount = 100_903,
TotalScore = 97_242_983_434,
RankedScore = 3_156_345_897,
PP = 9_568,
GradesCount = new UserStatistics.Grades
{
SS = 13_152,
S = 24_375,
A = 9_960
}
},
};
private void createPerformanceTable()
{ {
onLoadStarted(); onLoadStarted();
loadTable(new PerformanceTable(1, createUserStatistics()));
request = new GetUserRankingsRequest(ruleset, country: country, page: page);
((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
{
var table = new PerformanceTable(page, rankings.Users);
loadTable(table);
});
api.Queue(request);
} }
private void createScoreTable(RulesetInfo ruleset, int page = 1) private void createScoreTable()
{ {
onLoadStarted(); onLoadStarted();
loadTable(new ScoresTable(1, createUserStatistics()));
request = new GetUserRankingsRequest(ruleset, UserRankingsType.Score, page);
((GetUserRankingsRequest)request).Success += rankings => Schedule(() =>
{
var table = new ScoresTable(page, rankings.Users);
loadTable(table);
});
api.Queue(request);
}
private void createSpotlightTable(RulesetInfo ruleset, int spotlight)
{
onLoadStarted();
request = new GetSpotlightRankingsRequest(ruleset, spotlight, RankingsSortCriteria.All);
((GetSpotlightRankingsRequest)request).Success += rankings => Schedule(() =>
{
var table = new ScoresTable(1, rankings.Users);
loadTable(table);
});
api.Queue(request);
} }
private void onLoadStarted() private void onLoadStarted()
{ {
loading.Show(); loading.Show();
request?.Cancel();
cancellationToken?.Cancel(); cancellationToken?.Cancel();
cancellationToken = new CancellationTokenSource(); cancellationToken = new CancellationTokenSource();
} }

View File

@ -0,0 +1,57 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Timing;
using osu.Game.Overlays.BeatmapSet.Scores;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneScoreboardTime : OsuTestScene
{
private StopwatchClock stopwatch;
[Test]
public void TestVariousUnits()
{
AddStep("create various scoreboard times", () => Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = new FramedClock(stopwatch = new StopwatchClock()), // prevent time from naturally elapsing.
Direction = FillDirection.Vertical,
ChildrenEnumerable = testCases.Select(dateTime => new ScoreboardTime(dateTime, 24).With(time => time.Anchor = time.Origin = Anchor.TopCentre))
});
AddStep("start stopwatch", () => stopwatch.Start());
}
private static IEnumerable<DateTimeOffset> testCases => new[]
{
DateTimeOffset.Now,
DateTimeOffset.Now.AddSeconds(-1),
DateTimeOffset.Now.AddSeconds(-25),
DateTimeOffset.Now.AddSeconds(-59),
DateTimeOffset.Now.AddMinutes(-1),
DateTimeOffset.Now.AddMinutes(-25),
DateTimeOffset.Now.AddMinutes(-59),
DateTimeOffset.Now.AddHours(-1),
DateTimeOffset.Now.AddHours(-13),
DateTimeOffset.Now.AddHours(-23),
DateTimeOffset.Now.AddDays(-1),
DateTimeOffset.Now.AddDays(-6),
DateTimeOffset.Now.AddDays(-16),
DateTimeOffset.Now.AddMonths(-1),
DateTimeOffset.Now.AddMonths(-11),
DateTimeOffset.Now.AddYears(-1),
DateTimeOffset.Now.AddYears(-5)
};
}
}

View File

@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -142,7 +143,7 @@ namespace osu.Game.Tests.Visual.Playlists
modifiedBeatmap.HitObjects.Clear(); modifiedBeatmap.HitObjects.Clear();
modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 });
manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).Wait(); manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).WaitSafely();
}); });
// Create the room using the real beatmap values. // Create the room using the real beatmap values.
@ -184,7 +185,7 @@ namespace osu.Game.Tests.Visual.Playlists
}, },
}; };
manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).Wait(); manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).WaitSafely();
}); });
AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash);
@ -201,7 +202,7 @@ namespace osu.Game.Tests.Visual.Playlists
}); });
} }
private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Result); private void importBeatmap() => AddStep("import beatmap", () => importedBeatmap = manager.Import(CreateBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).GetResultSafely());
private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
{ {

View File

@ -6,6 +6,7 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep(@"Set beatmap", () => AddStep(@"Set beatmap", () =>
{ {
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
leaderboard.BeatmapInfo = beatmapInfo; leaderboard.BeatmapInfo = beatmapInfo;
@ -175,7 +176,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep(@"Load new scores via manager", () => AddStep(@"Load new scores via manager", () =>
{ {
foreach (var score in generateSampleScores(beatmapInfo())) foreach (var score in generateSampleScores(beatmapInfo()))
scoreManager.Import(score).Wait(); scoreManager.Import(score).WaitSafely();
}); });
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions; using osu.Game.Extensions;
@ -181,7 +182,7 @@ namespace osu.Game.Tests.Visual.SongSelect
beatmap.DifficultyName = $"SR{i + 1}"; beatmap.DifficultyName = $"SR{i + 1}";
} }
return Game.BeatmapManager.Import(beatmapSet).Result.Value; return Game.BeatmapManager.Import(beatmapSet).GetResultSafely().Value;
} }
private bool ensureAllBeatmapSetsImported(IEnumerable<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null); private bool ensureAllBeatmapSetsImported(IEnumerable<BeatmapSetInfo> beatmapSets) => beatmapSets.All(set => set != null);

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -38,7 +39,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); Dependencies.Cache(rulesets = new RulesetStore(ContextFactory));
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, Audio, Resources, host, Beatmap.Default));
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
{ {

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -256,7 +257,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import multi-ruleset map", () => AddStep("import multi-ruleset map", () =>
{ {
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).Wait(); manager.Import(TestResources.CreateTestBeatmapSetInfo(rulesets: usableRulesets)).WaitSafely();
}); });
} }
else else
@ -670,7 +671,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import multi-ruleset map", () => AddStep("import multi-ruleset map", () =>
{ {
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).Wait(); manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely();
}); });
int previousSetID = 0; int previousSetID = 0;
@ -710,7 +711,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import multi-ruleset map", () => AddStep("import multi-ruleset map", () =>
{ {
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).Wait(); manager.Import(TestResources.CreateTestBeatmapSetInfo(3, usableRulesets)).WaitSafely();
}); });
DrawableCarouselBeatmapSet set = null; DrawableCarouselBeatmapSet set = null;
@ -759,7 +760,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("import huge difficulty count map", () => AddStep("import huge difficulty count map", () =>
{ {
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).Result.Value; imported = manager.Import(TestResources.CreateTestBeatmapSetInfo(50, usableRulesets)).GetResultSafely().Value;
}); });
AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First()));
@ -868,7 +869,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).Wait(); private void importForRuleset(int id) => manager.Import(TestResources.CreateTestBeatmapSetInfo(3, rulesets.AvailableRulesets.Where(r => r.OnlineID == id).ToArray())).WaitSafely();
private void checkMusicPlaying(bool playing) => private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing); AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
@ -898,7 +899,7 @@ namespace osu.Game.Tests.Visual.SongSelect
var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray(); var usableRulesets = rulesets.AvailableRulesets.Where(r => r.OnlineID != 2).ToArray();
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).Wait(); manager.Import(TestResources.CreateTestBeatmapSetInfo(difficultyCountPerSet, usableRulesets)).WaitSafely();
}); });
} }

View File

@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -85,7 +86,7 @@ namespace osu.Game.Tests.Visual.UserInterface
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, ContextFactory, Scheduler));
beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0]; beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).GetResultSafely().Value.Beatmaps[0];
for (int i = 0; i < 50; i++) for (int i = 0; i < 50; i++)
{ {
@ -101,7 +102,7 @@ namespace osu.Game.Tests.Visual.UserInterface
User = new APIUser { Username = "TestUser" }, User = new APIUser { Username = "TestUser" },
}; };
importedScores.Add(scoreManager.Import(score).Result.Value); importedScores.Add(scoreManager.Import(score).GetResultSafely().Value);
} }
return dependencies; return dependencies;

View File

@ -0,0 +1,79 @@
// 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;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface.PageSelector;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.UserInterface
{
public class TestScenePageSelector : OsuTestScene
{
[Cached]
private OverlayColourProvider provider { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
private readonly PageSelector pageSelector;
public TestScenePageSelector()
{
AddRange(new Drawable[]
{
pageSelector = new PageSelector
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
});
}
[Test]
public void TestOmittedPages()
{
setAvailablePages(100);
selectPageIndex(0);
checkVisiblePageNumbers(new[] { 1, 2, 3, 100 });
selectPageIndex(6);
checkVisiblePageNumbers(new[] { 1, 5, 6, 7, 8, 9, 100 });
selectPageIndex(49);
checkVisiblePageNumbers(new[] { 1, 48, 49, 50, 51, 52, 100 });
selectPageIndex(99);
checkVisiblePageNumbers(new[] { 1, 98, 99, 100 });
}
[Test]
public void TestResetCurrentPage()
{
setAvailablePages(10);
selectPageIndex(6);
setAvailablePages(11);
AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0);
}
[Test]
public void TestOutOfBoundsSelection()
{
setAvailablePages(10);
selectPageIndex(11);
AddAssert("Page 10 is current", () => pageSelector.CurrentPage.Value == pageSelector.AvailablePages.Value - 1);
selectPageIndex(-1);
AddAssert("Page 1 is current", () => pageSelector.CurrentPage.Value == 0);
}
private void checkVisiblePageNumbers(int[] expected) => AddAssert($"Sequence is {string.Join(',', expected.Select(i => i.ToString()))}", () => pageSelector.ChildrenOfType<PageSelectorPageButton>().Select(p => p.PageNumber).SequenceEqual(expected));
private void selectPageIndex(int pageIndex) =>
AddStep($"Select page {pageIndex}", () => pageSelector.CurrentPage.Value = pageIndex);
private void setAvailablePages(int availablePages) =>
AddStep($"Set available pages to {availablePages}", () => pageSelector.AvailablePages.Value = availablePages);
}
}

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
this.api = api; this.api = api;
testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).GetResultSafely();
} }
[Test] [Test]

View File

@ -36,7 +36,7 @@ namespace osu.Game.Tournament.Tests.NonVisual
TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get<Storage>(); TournamentStorage storage = (TournamentStorage)osu.Dependencies.Get<Storage>();
FileBasedIPC ipc = null; FileBasedIPC ipc = null;
WaitForOrAssert(() => (ipc = osu.Dependencies.Get<MatchIPCInfo>() as FileBasedIPC) != null, @"ipc could not be populated in a reasonable amount of time"); WaitForOrAssert(() => (ipc = osu.Dependencies.Get<MatchIPCInfo>() as FileBasedIPC)?.IsLoaded == true, @"ipc could not be populated in a reasonable amount of time");
Assert.True(ipc.SetIPCLocation(testStableInstallDirectory)); Assert.True(ipc.SetIPCLocation(testStableInstallDirectory));
Assert.True(storage.AllTournaments.Exists("stable.json")); Assert.True(storage.AllTournaments.Exists("stable.json"));

View File

@ -93,8 +93,12 @@ namespace osu.Game.Beatmaps
if (t.Time > lastTime) if (t.Time > lastTime)
return (beatLength: t.BeatLength, 0); return (beatLength: t.BeatLength, 0);
// osu-stable forced the first control point to start at 0.
// This is reproduced here to maintain compatibility around osu!mania scroll speed and song select display.
double currentTime = i == 0 ? 0 : t.Time;
double nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time; double nextTime = i == ControlPointInfo.TimingPoints.Count - 1 ? lastTime : ControlPointInfo.TimingPoints[i + 1].Time;
return (beatLength: t.BeatLength, duration: nextTime - t.Time);
return (beatLength: t.BeatLength, duration: nextTime - currentTime);
}) })
// Aggregate durations into a set of (beatLength, duration) tuples for each beat length // Aggregate durations into a set of (beatLength, duration) tuples for each beat length
.GroupBy(t => Math.Round(t.beatLength * 1000) / 1000) .GroupBy(t => Math.Round(t.beatLength * 1000) / 1000)

View File

@ -10,6 +10,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Threading; using osu.Framework.Threading;
@ -261,13 +262,18 @@ namespace osu.Game.Beatmaps
// GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available // GetDifficultyAsync will fall back to existing data from IBeatmapInfo if not locally available
// (contrary to GetAsync) // (contrary to GetAsync)
GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken) GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken)
.ContinueWith(t => .ContinueWith(task =>
{ {
// We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events.
Schedule(() => Schedule(() =>
{ {
if (!cancellationToken.IsCancellationRequested && t.Result != null) if (cancellationToken.IsCancellationRequested)
bindable.Value = t.Result; return;
var starDifficulty = task.GetResultSafely();
if (starDifficulty != null)
bindable.Value = starDifficulty.Value;
}); });
}, cancellationToken); }, cancellationToken);
} }

View File

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -91,7 +92,7 @@ namespace osu.Game.Beatmaps
} }
}; };
var imported = beatmapModelManager.Import(set).Result.Value; var imported = beatmapModelManager.Import(set).GetResultSafely().Value;
return GetWorkingBeatmap(imported.Beatmaps.First()); return GetWorkingBeatmap(imported.Beatmaps.First());
} }

View File

@ -3,16 +3,13 @@
#nullable enable #nullable enable
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Overlays; using osu.Game.Overlays;
using osuTK; using osuTK;
@ -156,54 +153,5 @@ namespace osu.Game.Beatmaps.Drawables.Cards
Hollow = true, Hollow = true,
}, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
} }
private class ExpandedContentScrollContainer : OsuScrollContainer
{
public ExpandedContentScrollContainer()
{
ScrollbarVisible = false;
}
protected override void Update()
{
base.Update();
Height = Math.Min(Content.DrawHeight, 400);
}
private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize);
protected override bool OnDragStart(DragStartEvent e)
{
if (!allowScroll)
return false;
return base.OnDragStart(e);
}
protected override void OnDrag(DragEvent e)
{
if (!allowScroll)
return;
base.OnDrag(e);
}
protected override void OnDragEnd(DragEndEvent e)
{
if (!allowScroll)
return;
base.OnDragEnd(e);
}
protected override bool OnScroll(ScrollEvent e)
{
if (!allowScroll)
return false;
return base.OnScroll(e);
}
}
} }
} }

View File

@ -0,0 +1,61 @@
// 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.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics.Containers;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class ExpandedContentScrollContainer : OsuScrollContainer
{
public const float HEIGHT = 200;
public ExpandedContentScrollContainer()
{
ScrollbarVisible = false;
}
protected override void Update()
{
base.Update();
Height = Math.Min(Content.DrawHeight, HEIGHT);
}
private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize);
protected override bool OnDragStart(DragStartEvent e)
{
if (!allowScroll)
return false;
return base.OnDragStart(e);
}
protected override void OnDrag(DragEvent e)
{
if (!allowScroll)
return;
base.OnDrag(e);
}
protected override void OnDragEnd(DragEndEvent e)
{
if (!allowScroll)
return;
base.OnDragEnd(e);
}
protected override bool OnScroll(ScrollEvent e)
{
if (!allowScroll)
return false;
return base.OnScroll(e);
}
}
}

View File

@ -11,6 +11,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Testing; using osu.Framework.Testing;
@ -185,7 +186,7 @@ namespace osu.Game.Beatmaps
{ {
try try
{ {
return loadBeatmapAsync().Result; return loadBeatmapAsync().GetResultSafely();
} }
catch (AggregateException ae) catch (AggregateException ae)
{ {

View File

@ -98,6 +98,8 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.MenuParallax, true); SetDefault(OsuSetting.MenuParallax, true);
// Gameplay // Gameplay
SetDefault(OsuSetting.PositionalHitsounds, true); // replaced by level setting below, can be removed 20220703.
SetDefault(OsuSetting.PositionalHitsoundsLevel, 0.2f, 0, 1);
SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01); SetDefault(OsuSetting.DimLevel, 0.8, 0, 1, 0.01);
SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01); SetDefault(OsuSetting.BlurLevel, 0, 0, 1, 0.01);
SetDefault(OsuSetting.LightenDuringBreaks, true); SetDefault(OsuSetting.LightenDuringBreaks, true);
@ -109,7 +111,6 @@ namespace osu.Game.Configuration
SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true);
SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true); SetDefault(OsuSetting.FadePlayfieldWhenHealthLow, true);
SetDefault(OsuSetting.KeyOverlay, false); SetDefault(OsuSetting.KeyOverlay, false);
SetDefault(OsuSetting.PositionalHitSounds, true);
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true); SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
SetDefault(OsuSetting.FloatingComments, false); SetDefault(OsuSetting.FloatingComments, false);
@ -175,9 +176,11 @@ namespace osu.Game.Configuration
int combined = (year * 10000) + monthDay; int combined = (year * 10000) + monthDay;
if (combined < 20210413) if (combined < 20220103)
{ {
SetValue(OsuSetting.EditorWaveformOpacity, 0.25f); var positionalHitsoundsEnabled = GetBindable<bool>(OsuSetting.PositionalHitsounds);
if (!positionalHitsoundsEnabled.Value)
SetValue(OsuSetting.PositionalHitsoundsLevel, 0);
} }
} }
@ -256,7 +259,8 @@ namespace osu.Game.Configuration
LightenDuringBreaks, LightenDuringBreaks,
ShowStoryboard, ShowStoryboard,
KeyOverlay, KeyOverlay,
PositionalHitSounds, PositionalHitsounds,
PositionalHitsoundsLevel,
AlwaysPlayFirstComboBreak, AlwaysPlayFirstComboBreak,
FloatingComments, FloatingComments,
HUDVisibilityMode, HUDVisibilityMode,

View File

@ -8,6 +8,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Game.Online.API; using osu.Game.Online.API;
namespace osu.Game.Database namespace osu.Game.Database
@ -58,7 +59,7 @@ namespace osu.Game.Database
if (!task.IsCompletedSuccessfully) if (!task.IsCompletedSuccessfully)
return null; return null;
return task.Result; return task.GetResultSafely();
}, token)); }, token));
} }

View File

@ -26,6 +26,9 @@ namespace osu.Game.Database
/// <summary> /// <summary>
/// Create a detached copy of the each item in the collection. /// Create a detached copy of the each item in the collection.
/// </summary> /// </summary>
/// <remarks>
/// Items which are already detached (ie. not managed by realm) will not be modified.
/// </remarks>
/// <param name="items">A list of managed <see cref="RealmObject"/>s to detach.</param> /// <param name="items">A list of managed <see cref="RealmObject"/>s to detach.</param>
/// <typeparam name="T">The type of object.</typeparam> /// <typeparam name="T">The type of object.</typeparam>
/// <returns>A list containing non-managed copies of provided items.</returns> /// <returns>A list containing non-managed copies of provided items.</returns>
@ -42,6 +45,9 @@ namespace osu.Game.Database
/// <summary> /// <summary>
/// Create a detached copy of the item. /// Create a detached copy of the item.
/// </summary> /// </summary>
/// <remarks>
/// If the item if already detached (ie. not managed by realm) it will not be detached again and the original instance will be returned. This allows this method to be potentially called at multiple levels while only incurring the clone overhead once.
/// </remarks>
/// <param name="item">The managed <see cref="RealmObject"/> to detach.</param> /// <param name="item">The managed <see cref="RealmObject"/> to detach.</param>
/// <typeparam name="T">The type of object.</typeparam> /// <typeparam name="T">The type of object.</typeparam>
/// <returns>A non-managed copy of provided item. Will return the provided item if already detached.</returns> /// <returns>A non-managed copy of provided item. Will return the provided item if already detached.</returns>

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Globalization;
using Newtonsoft.Json.Linq;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
@ -16,7 +18,7 @@ namespace osu.Game.Extensions
{ {
cursor?.Properties.ForEach(x => cursor?.Properties.ForEach(x =>
{ {
webRequest.AddParameter("cursor[" + x.Key + "]", x.Value.ToString()); webRequest.AddParameter("cursor[" + x.Key + "]", (x.Value as JValue)?.ToString(CultureInfo.InvariantCulture) ?? x.Value.ToString());
}); });
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -17,8 +18,6 @@ namespace osu.Game.Graphics.Backgrounds
/// </summary> /// </summary>
public class Background : CompositeDrawable, IEquatable<Background> public class Background : CompositeDrawable, IEquatable<Background>
{ {
private const float blur_scale = 0.5f;
public readonly Sprite Sprite; public readonly Sprite Sprite;
private readonly string textureName; private readonly string textureName;
@ -46,7 +45,7 @@ namespace osu.Game.Graphics.Backgrounds
Sprite.Texture = textures.Get(textureName); Sprite.Texture = textures.Get(textureName);
} }
public Vector2 BlurSigma => bufferedContainer?.BlurSigma / blur_scale ?? Vector2.Zero; public Vector2 BlurSigma => Vector2.Divide(bufferedContainer?.BlurSigma ?? Vector2.Zero, blurScale);
/// <summary> /// <summary>
/// Smoothly adjusts <see cref="IBufferedContainer.BlurSigma"/> over time. /// Smoothly adjusts <see cref="IBufferedContainer.BlurSigma"/> over time.
@ -67,9 +66,48 @@ namespace osu.Game.Graphics.Backgrounds
} }
if (bufferedContainer != null) if (bufferedContainer != null)
bufferedContainer.FrameBufferScale = newBlurSigma == Vector2.Zero ? Vector2.One : new Vector2(blur_scale); transformBlurSigma(newBlurSigma, duration, easing);
}
bufferedContainer?.BlurTo(newBlurSigma * blur_scale, duration, easing); private void transformBlurSigma(Vector2 newBlurSigma, double duration, Easing easing)
=> this.TransformTo(nameof(blurSigma), newBlurSigma, duration, easing);
private Vector2 blurSigmaBacking = Vector2.Zero;
private Vector2 blurScale = Vector2.One;
private Vector2 blurSigma
{
get => blurSigmaBacking;
set
{
Debug.Assert(bufferedContainer != null);
blurSigmaBacking = value;
blurScale = new Vector2(calculateBlurDownscale(value.X), calculateBlurDownscale(value.Y));
bufferedContainer.FrameBufferScale = blurScale;
bufferedContainer.BlurSigma = value * blurScale; // If the image is scaled down, the blur radius also needs to be reduced to cover the same pixel block.
}
}
/// <summary>
/// Determines a factor to downscale the background based on a given blur sigma, in order to reduce the computational complexity of blurs.
/// </summary>
/// <param name="sigma">The blur sigma.</param>
/// <returns>The scale-down factor.</returns>
private float calculateBlurDownscale(float sigma)
{
// If we're blurring within one pixel, scaling down will always result in an undesirable loss of quality.
// The algorithm below would also cause this value to go above 1, which is likewise undesirable.
if (sigma <= 1)
return 1;
// A good value is one where the loss in quality as a result of downscaling the image is not easily perceivable.
// The constants here have been experimentally chosen to yield nice transitions by approximating a log curve through the points {{ 1, 1 }, { 4, 0.75 }, { 16, 0.5 }, { 32, 0.25 }}.
float scale = -0.18f * MathF.Log(0.004f * sigma);
// To reduce shimmering, the scaling transitions are limited to happen only in increments of 0.2.
return MathF.Round(scale / 0.2f, MidpointRounding.AwayFromZero) * 0.2f;
} }
public virtual bool Equals(Background other) public virtual bool Equals(Background other)

View File

@ -1,20 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable enable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
namespace osu.Game.Graphics.Backgrounds namespace osu.Game.Graphics.Backgrounds
{ {
public class BeatmapBackgroundWithStoryboard : BeatmapBackground public class BeatmapBackgroundWithStoryboard : BeatmapBackground
{ {
private readonly InterpolatingFramedClock storyboardClock;
[Resolved(CanBeNull = true)]
private MusicController? musicController { get; set; }
public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1") public BeatmapBackgroundWithStoryboard(WorkingBeatmap beatmap, string fallbackTextureName = "Backgrounds/bg1")
: base(beatmap, fallbackTextureName) : base(beatmap, fallbackTextureName)
{ {
storyboardClock = new InterpolatingFramedClock();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -30,8 +39,40 @@ namespace osu.Game.Graphics.Backgrounds
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Volume = { Value = 0 }, Volume = { Value = 0 },
Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = new InterpolatingFramedClock(Beatmap.Track) } Child = new DrawableStoryboard(Beatmap.Storyboard) { Clock = storyboardClock }
}, AddInternal); }, AddInternal);
} }
protected override void LoadComplete()
{
base.LoadComplete();
if (musicController != null)
musicController.TrackChanged += onTrackChanged;
updateStoryboardClockSource(Beatmap);
}
private void onTrackChanged(WorkingBeatmap newBeatmap, TrackChangeDirection _) => updateStoryboardClockSource(newBeatmap);
private void updateStoryboardClockSource(WorkingBeatmap newBeatmap)
{
if (newBeatmap != Beatmap)
return;
// `MusicController` will sometimes reload the track, even when the working beatmap technically hasn't changed.
// ensure that the storyboard's clock is always using the latest track instance.
storyboardClock.ChangeSource(newBeatmap.Track);
// more often than not, the previous source track's time will be in the future relative to the new source track.
// explicitly process a single frame so that `InterpolatingFramedClock`'s interpolation logic is bypassed
// and the storyboard clock is correctly rewound to the source track's time exactly.
storyboardClock.ProcessFrame();
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (musicController != null)
musicController.TrackChanged -= onTrackChanged;
}
} }
} }

View File

@ -0,0 +1,33 @@
// 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.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
internal class PageEllipsis : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
Text = "...",
Colour = colourProvider.Light3,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
};
}
}
}

View File

@ -0,0 +1,102 @@
// 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.Containers;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public class PageSelector : CompositeDrawable
{
public readonly BindableInt CurrentPage = new BindableInt { MinValue = 0, };
public readonly BindableInt AvailablePages = new BindableInt(1) { MinValue = 1, };
private readonly FillFlowContainer itemsFlow;
private readonly PageSelectorPrevNextButton previousPageButton;
private readonly PageSelectorPrevNextButton nextPageButton;
public PageSelector()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
previousPageButton = new PageSelectorPrevNextButton(false, "prev")
{
Action = () => CurrentPage.Value -= 1,
},
itemsFlow = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
},
nextPageButton = new PageSelectorPrevNextButton(true, "next")
{
Action = () => CurrentPage.Value += 1
}
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
CurrentPage.BindValueChanged(_ => Scheduler.AddOnce(redraw));
AvailablePages.BindValueChanged(_ =>
{
CurrentPage.Value = 0;
// AddOnce as the reset of CurrentPage may also trigger a redraw.
Scheduler.AddOnce(redraw);
}, true);
}
private void redraw()
{
if (CurrentPage.Value >= AvailablePages.Value)
{
CurrentPage.Value = AvailablePages.Value - 1;
return;
}
previousPageButton.Enabled.Value = CurrentPage.Value != 0;
nextPageButton.Enabled.Value = CurrentPage.Value < AvailablePages.Value - 1;
itemsFlow.Clear();
int totalPages = AvailablePages.Value;
bool lastWasEllipsis = false;
for (int i = 0; i < totalPages; i++)
{
int pageIndex = i;
bool shouldShowPage = pageIndex == 0 || pageIndex == totalPages - 1 || Math.Abs(pageIndex - CurrentPage.Value) <= 2;
if (shouldShowPage)
{
lastWasEllipsis = false;
itemsFlow.Add(new PageSelectorPageButton(pageIndex + 1)
{
Action = () => CurrentPage.Value = pageIndex,
Selected = CurrentPage.Value == pageIndex,
});
}
else if (!lastWasEllipsis)
{
lastWasEllipsis = true;
itemsFlow.Add(new PageEllipsis());
}
}
}
}
}

View File

@ -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 osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Events;
using JetBrains.Annotations;
using osu.Game.Overlays;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public abstract class PageSelectorButton : OsuClickableContainer
{
protected const int DURATION = 200;
[Resolved]
protected OverlayColourProvider ColourProvider { get; private set; }
protected Box Background;
protected PageSelectorButton()
{
AutoSizeAxes = Axes.X;
Height = 20;
}
[BackgroundDependencyLoader]
private void load()
{
Add(new CircularContainer
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Masking = true,
Children = new[]
{
Background = new Box
{
RelativeSizeAxes = Axes.Both
},
CreateContent().With(content =>
{
content.Anchor = Anchor.Centre;
content.Origin = Anchor.Centre;
content.Margin = new MarginPadding { Horizontal = 10 };
})
}
});
}
[NotNull]
protected abstract Drawable CreateContent();
protected override bool OnHover(HoverEvent e)
{
UpdateHoverState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
UpdateHoverState();
}
protected abstract void UpdateHoverState();
}
}

View File

@ -0,0 +1,70 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Framework.Allocation;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public class PageSelectorPageButton : PageSelectorButton
{
private readonly BindableBool selected = new BindableBool();
public bool Selected
{
set => selected.Value = value;
}
public int PageNumber { get; }
private OsuSpriteText text;
public PageSelectorPageButton(int pageNumber)
{
PageNumber = pageNumber;
Action = () =>
{
if (!selected.Value)
selected.Value = true;
};
}
protected override Drawable CreateContent() => text = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold),
Text = PageNumber.ToString(),
};
[BackgroundDependencyLoader]
private void load()
{
Background.Colour = ColourProvider.Highlight1;
Background.Alpha = 0;
}
protected override void LoadComplete()
{
base.LoadComplete();
selected.BindValueChanged(onSelectedChanged, true);
}
private void onSelectedChanged(ValueChangedEvent<bool> selected)
{
Background.FadeTo(selected.NewValue ? 1 : 0, DURATION, Easing.OutQuint);
text.FadeColour(selected.NewValue ? ColourProvider.Dark4 : ColourProvider.Light3, DURATION, Easing.OutQuint);
text.Font = text.Font.With(weight: IsHovered ? FontWeight.SemiBold : FontWeight.Regular);
}
protected override void UpdateHoverState()
{
if (selected.Value)
return;
text.FadeColour(IsHovered ? ColourProvider.Light2 : ColourProvider.Light1, DURATION, Easing.OutQuint);
}
}
}

View File

@ -0,0 +1,78 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Graphics.UserInterface.PageSelector
{
public class PageSelectorPrevNextButton : PageSelectorButton
{
private readonly bool rightAligned;
private readonly string text;
private SpriteIcon icon;
private OsuSpriteText name;
public PageSelectorPrevNextButton(bool rightAligned, string text)
{
this.rightAligned = rightAligned;
this.text = text;
}
protected override Drawable CreateContent() => new Container
{
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
name = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12),
Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
Text = text.ToUpper(),
},
icon = new SpriteIcon
{
Icon = rightAligned ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft,
Size = new Vector2(8),
Anchor = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
Origin = rightAligned ? Anchor.CentreLeft : Anchor.CentreRight,
},
}
},
}
};
[BackgroundDependencyLoader]
private void load()
{
Background.Colour = ColourProvider.Dark4;
name.Colour = icon.Colour = ColourProvider.Light1;
}
protected override void LoadComplete()
{
base.LoadComplete();
Enabled.BindValueChanged(enabled => Background.FadeTo(enabled.NewValue ? 1 : 0.5f, DURATION), true);
}
protected override void UpdateHoverState() =>
Background.FadeColour(IsHovered ? ColourProvider.Dark3 : ColourProvider.Dark4, DURATION, Easing.OutQuint);
}
}

View File

@ -32,7 +32,18 @@ namespace osu.Game.IO.Archives
public abstract IEnumerable<string> Filenames { get; } public abstract IEnumerable<string> Filenames { get; }
public virtual byte[] Get(string name) => GetAsync(name).Result; public virtual byte[] Get(string name)
{
using (Stream input = GetStream(name))
{
if (input == null)
return null;
byte[] buffer = new byte[input.Length];
input.Read(buffer);
return buffer;
}
}
public async Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = default) public async Task<byte[]> GetAsync(string name, CancellationToken cancellationToken = default)
{ {

View File

@ -77,6 +77,8 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally),
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
}; };
public IEnumerable<KeyBinding> InGameKeyBindings => new[] public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@ -292,6 +294,12 @@ namespace osu.Game.Input.Bindings
EditorCycleGridDisplayMode, EditorCycleGridDisplayMode,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))]
EditorTestGameplay EditorTestGameplay,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipHorizontally))]
EditorFlipHorizontally,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorFlipVertically))]
EditorFlipVertically,
} }
} }

View File

@ -1,11 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Platform;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
namespace osu.Game.Input namespace osu.Game.Input
@ -63,8 +65,17 @@ namespace osu.Game.Input
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) => updateLastInteractionTime(); public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) => updateLastInteractionTime();
[Resolved]
private GameHost host { get; set; }
protected override bool Handle(UIEvent e) protected override bool Handle(UIEvent e)
{ {
// Even when not active, `MouseMoveEvent`s will arrive.
// We don't want these to trigger a non-idle state as it's quite often the user interacting
// with other windows while osu! is in the background.
if (!host.IsActive.Value)
return base.Handle(e);
switch (e) switch (e)
{ {
case KeyDownEvent _: case KeyDownEvent _:

View File

@ -32,6 +32,11 @@ namespace osu.Game.Localisation
/// <summary> /// <summary>
/// "Master" /// "Master"
/// </summary> /// </summary>
public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation");
/// <summary>
/// "Level"
/// </summary>
public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master"); public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
/// <summary> /// <summary>

View File

@ -44,6 +44,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches"); public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches");
/// <summary>
/// "Compact realm"
/// </summary>
public static LocalisableString CompactRealm => new TranslatableString(getKey(@"compact_realm"), @"Compact realm");
private static string getKey(string key) => $"{prefix}:{key}"; private static string getKey(string key) => $"{prefix}:{key}";
} }
} }

View File

@ -84,11 +84,6 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay"); public static LocalisableString AlwaysShowKeyOverlay => new TranslatableString(getKey(@"key_overlay"), @"Always show key overlay");
/// <summary>
/// "Positional hitsounds"
/// </summary>
public static LocalisableString PositionalHitsounds => new TranslatableString(getKey(@"positional_hitsounds"), @"Positional hitsounds");
/// <summary> /// <summary>
/// "Always play first combo break sound" /// "Always play first combo break sound"
/// </summary> /// </summary>

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