1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 11:02:54 +08:00

Merge branch 'master' into multi-queueing-modes

This commit is contained in:
Dan Balasescu 2021-11-12 22:16:04 +09:00
commit 3b24ec3643
97 changed files with 1031 additions and 279 deletions

View File

@ -51,7 +51,7 @@
<Reference Include="Java.Interop" /> <Reference Include="Java.Interop" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1108.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2021.1108.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">

View File

@ -0,0 +1,185 @@
// 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.Utils;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public class TestSceneSliderStreamConversion : TestSceneOsuEditor
{
private BindableBeatDivisor beatDivisor => (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor));
[Test]
public void TestSimpleConversion()
{
Slider slider = null;
AddStep("select first slider", () =>
{
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
EditorClock.Seek(slider.StartTime);
EditorBeatmap.SelectedHitObjects.Add(slider);
});
convertToStream();
AddAssert("stream created", () => streamCreatedFor(slider,
(time: 0, pathPosition: 0),
(time: 0.25, pathPosition: 0.25),
(time: 0.5, pathPosition: 0.5),
(time: 0.75, pathPosition: 0.75),
(time: 1, pathPosition: 1)));
AddStep("undo", () => Editor.Undo());
AddAssert("slider restored", () => sliderRestored(slider));
AddStep("select first slider", () =>
{
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider);
EditorClock.Seek(slider.StartTime);
EditorBeatmap.SelectedHitObjects.Add(slider);
});
AddStep("change beat divisor", () => beatDivisor.Value = 8);
convertToStream();
AddAssert("stream created", () => streamCreatedFor(slider,
(time: 0, pathPosition: 0),
(time: 0.125, pathPosition: 0.125),
(time: 0.25, pathPosition: 0.25),
(time: 0.375, pathPosition: 0.375),
(time: 0.5, pathPosition: 0.5),
(time: 0.625, pathPosition: 0.625),
(time: 0.75, pathPosition: 0.75),
(time: 0.875, pathPosition: 0.875),
(time: 1, pathPosition: 1)));
}
[Test]
public void TestConversionWithNonMatchingDivisor()
{
Slider slider = null;
AddStep("select second slider", () =>
{
slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider).ElementAt(1);
EditorClock.Seek(slider.StartTime);
EditorBeatmap.SelectedHitObjects.Add(slider);
});
AddStep("change beat divisor", () => beatDivisor.Value = 3);
convertToStream();
AddAssert("stream created", () => streamCreatedFor(slider,
(time: 0, pathPosition: 0),
(time: 2 / 3d, pathPosition: 2 / 3d)));
}
[Test]
public void TestConversionWithRepeats()
{
Slider slider = null;
AddStep("select first slider with repeats", () =>
{
slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider s && s.RepeatCount > 0);
EditorClock.Seek(slider.StartTime);
EditorBeatmap.SelectedHitObjects.Add(slider);
});
AddStep("change beat divisor", () => beatDivisor.Value = 2);
convertToStream();
AddAssert("stream created", () => streamCreatedFor(slider,
(time: 0, pathPosition: 0),
(time: 0.25, pathPosition: 0.5),
(time: 0.5, pathPosition: 1),
(time: 0.75, pathPosition: 0.5),
(time: 1, pathPosition: 0)));
}
[Test]
public void TestConversionPreservesSliderProperties()
{
Slider slider = null;
AddStep("select second new-combo-starting slider", () =>
{
slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider s && s.NewCombo).ElementAt(1);
EditorClock.Seek(slider.StartTime);
EditorBeatmap.SelectedHitObjects.Add(slider);
});
convertToStream();
AddAssert("stream created", () => streamCreatedFor(slider,
(time: 0, pathPosition: 0),
(time: 0.25, pathPosition: 0.25),
(time: 0.5, pathPosition: 0.5),
(time: 0.75, pathPosition: 0.75),
(time: 1, pathPosition: 1)));
AddStep("undo", () => Editor.Undo());
AddAssert("slider restored", () => sliderRestored(slider));
}
private void convertToStream()
{
AddStep("convert to stream", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.PressKey(Key.LShift);
InputManager.Key(Key.F);
InputManager.ReleaseKey(Key.LShift);
InputManager.ReleaseKey(Key.LControl);
});
}
private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles)
{
if (EditorBeatmap.HitObjects.Contains(slider))
return false;
foreach ((double expectedTime, double expectedPathPosition) in expectedCircles)
{
double time = slider.StartTime + slider.Duration * expectedTime;
Vector2 position = slider.Position + slider.Path.PositionAt(expectedPathPosition);
if (!EditorBeatmap.HitObjects.OfType<HitCircle>().Any(h => matches(h, time, position, slider.NewCombo && expectedTime == 0)))
return false;
}
return true;
bool matches(HitCircle circle, double time, Vector2 position, bool startsNewCombo) =>
Precision.AlmostEquals(circle.StartTime, time, 1)
&& Precision.AlmostEquals(circle.Position, position, 0.01f)
&& circle.NewCombo == startsNewCombo
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples)
&& circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint);
}
private bool sliderRestored(Slider slider)
{
var objects = EditorBeatmap.HitObjects.Where(h => h.StartTime >= slider.StartTime && h.GetEndTime() <= slider.EndTime).ToList();
if (objects.Count > 1)
return false;
var hitObject = objects.Single();
if (!(hitObject is Slider restoredSlider))
return false;
return Precision.AlmostEquals(slider.StartTime, restoredSlider.StartTime)
&& Precision.AlmostEquals(slider.GetEndTime(), restoredSlider.GetEndTime())
&& Precision.AlmostEquals(slider.Position, restoredSlider.Position, 0.01f)
&& Precision.AlmostEquals(slider.EndPosition, restoredSlider.EndPosition, 0.01f);
}
}
}

View File

@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.6975550434910005d, "diffcalc-test")] [TestCase(6.6972307565739273d, "diffcalc-test")]
[TestCase(1.4670676815251105d, "zero-length-sliders")] [TestCase(1.4484754139145539d, "zero-length-sliders")]
public void Test(double expected, string name) public void Test(double expected, string name)
=> base.Test(expected, name); => base.Test(expected, name);
[TestCase(8.9389769779826267d, "diffcalc-test")] [TestCase(8.9382559208689809d, "diffcalc-test")]
[TestCase(1.7786917985891204d, "zero-length-sliders")] [TestCase(1.7548875851757628d, "zero-length-sliders")]
public void TestClockRateAdjusted(double expected, string name) public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime()); => Test(expected, name, new OsuModDoubleTime());

View File

@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
public double AimStrain { get; set; } public double AimStrain { get; set; }
public double SpeedStrain { get; set; } public double SpeedStrain { get; set; }
public double FlashlightRating { get; set; } public double FlashlightRating { get; set; }
public double SliderFactor { get; set; }
public double ApproachRate { get; set; } public double ApproachRate { get; set; }
public double OverallDifficulty { get; set; } public double OverallDifficulty { get; set; }
public double DrainRate { get; set; } public double DrainRate { get; set; }

View File

@ -34,8 +34,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; return new OsuDifficultyAttributes { Mods = mods, Skills = skills };
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier;
double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier;
double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1;
if (mods.Any(h => h is OsuModRelax)) if (mods.Any(h => h is OsuModRelax))
speedRating = 0.0; speedRating = 0.0;
@ -74,6 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
AimStrain = aimRating, AimStrain = aimRating,
SpeedStrain = speedRating, SpeedStrain = speedRating,
FlashlightRating = flashlightRating, FlashlightRating = flashlightRating,
SliderFactor = sliderFactor,
ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5,
OverallDifficulty = (80 - hitWindowGreat) / 6, OverallDifficulty = (80 - hitWindowGreat) / 6,
DrainRate = drainRate, DrainRate = drainRate,
@ -108,7 +112,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty
return new Skill[] return new Skill[]
{ {
new Aim(mods), new Aim(mods, true),
new Aim(mods, false),
new Speed(mods, hitWindowGreat), new Speed(mods, hitWindowGreat),
new Flashlight(mods) new Flashlight(mods)
}; };

View File

@ -125,6 +125,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty
aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate);
} }
// We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator.
double estimateDifficultSliders = Attributes.SliderCount * 0.15;
if (Attributes.SliderCount > 0)
{
double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor;
aimValue *= sliderNerfFactor;
}
aimValue *= accuracy; aimValue *= accuracy;
// It is important to also consider accuracy difficulty when doing that. // It is important to also consider accuracy difficulty when doing that.
aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500;

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths.
private const int min_delta_time = 25; private const int min_delta_time = 25;
private const float maximum_slider_radius = normalized_radius * 2.4f; private const float maximum_slider_radius = normalized_radius * 2.4f;
private const float assumed_slider_radius = normalized_radius * 1.65f; private const float assumed_slider_radius = normalized_radius * 1.8f;
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;

View File

@ -14,11 +14,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// </summary> /// </summary>
public class Aim : OsuStrainSkill public class Aim : OsuStrainSkill
{ {
public Aim(Mod[] mods) public Aim(Mod[] mods, bool withSliders)
: base(mods) : base(mods)
{ {
this.withSliders = withSliders;
} }
private readonly bool withSliders;
protected override int HistoryLength => 2; protected override int HistoryLength => 2;
private const double wide_angle_multiplier = 1.5; private const double wide_angle_multiplier = 1.5;
@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
// But if the last object is a slider, then we extend the travel velocity through the slider into the current object. // But if the last object is a slider, then we extend the travel velocity through the slider into the current object.
if (osuLastObj.BaseObject is Slider) if (osuLastObj.BaseObject is Slider && withSliders)
{ {
double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object
double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end. double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end.
@ -55,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
// As above, do the same for the previous hitobject. // As above, do the same for the previous hitobject.
double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
if (osuLastLastObj.BaseObject is Slider) if (osuLastLastObj.BaseObject is Slider && withSliders)
{ {
double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
@ -135,7 +138,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier); aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier);
// Add in additional slider velocity bonus. // Add in additional slider velocity bonus.
aimStrain += sliderBonus * slider_multiplier; if (withSliders)
aimStrain += sliderBonus * slider_multiplier;
return aimStrain; return aimStrain;
} }

View File

@ -11,9 +11,12 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit; 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.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
@ -47,6 +50,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
private IEditorChangeHandler changeHandler { get; set; } private IEditorChangeHandler changeHandler { get; set; }
[Resolved(CanBeNull = true)]
private BindableBeatDivisor beatDivisor { get; set; }
public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad; public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad;
private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>(); private readonly BindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
@ -173,6 +179,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
} }
} }
protected override bool OnKeyDown(KeyDownEvent e)
{
if (!IsSelected)
return false;
if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed)
{
convertToStream();
return true;
}
return false;
}
private int addControlPoint(Vector2 position) private int addControlPoint(Vector2 position)
{ {
position -= HitObject.Position; position -= HitObject.Position;
@ -234,9 +254,56 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
editorBeatmap?.Update(HitObject); editorBeatmap?.Update(HitObject);
} }
private void convertToStream()
{
if (editorBeatmap == null || changeHandler == null || beatDivisor == null)
return;
var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime);
double streamSpacing = timingPoint.BeatLength / beatDivisor.Value;
changeHandler.BeginChange();
int i = 0;
double time = HitObject.StartTime;
while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1))
{
// positionWithRepeats is a fractional number in the range of [0, HitObject.SpanCount()]
// and indicates how many fractional spans of a slider have passed up to time.
double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount();
double pathPosition = positionWithRepeats - (int)positionWithRepeats;
// every second span is in the reverse direction - need to reverse the path position.
if (Precision.AlmostBigger(positionWithRepeats % 2, 1))
pathPosition = 1 - pathPosition;
Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition);
var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone();
samplePoint.Time = time;
editorBeatmap.Add(new HitCircle
{
StartTime = time,
Position = position,
NewCombo = i == 0 && HitObject.NewCombo,
SampleControlPoint = samplePoint,
Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList()
});
i += 1;
time = HitObject.StartTime + i * streamSpacing;
}
editorBeatmap.Remove(HitObject);
changeHandler.EndChange();
}
public override MenuItem[] ContextMenuItems => new MenuItem[] public override MenuItem[] ContextMenuItems => new MenuItem[]
{ {
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream),
}; };
// Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions. // Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions.

View File

@ -116,8 +116,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual("Insane", beatmapInfo.DifficultyName); Assert.AreEqual("Insane", beatmapInfo.DifficultyName);
Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual(string.Empty, metadata.Source);
Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags);
Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); Assert.AreEqual(557821, beatmapInfo.OnlineID);
Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID); Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineID);
} }
} }

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
{ {
var beatmap = decodeAsJson(normal); var beatmap = decodeAsJson(normal);
var meta = beatmap.BeatmapInfo.Metadata; var meta = beatmap.BeatmapInfo.Metadata;
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);

View File

@ -18,6 +18,7 @@ using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -387,6 +388,41 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
[Test]
public async Task TestModelCreationFailureDoesntReturn()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
{
try
{
var osu = LoadOsuIntoHost(host);
var importer = osu.Dependencies.Get<BeatmapManager>();
var progressNotification = new ImportProgressNotification();
var zipStream = new MemoryStream();
using (var zip = ZipArchive.Create())
zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate));
var imported = await importer.Import(
progressNotification,
new ImportTask(zipStream, string.Empty)
);
checkBeatmapSetCount(osu, 0);
checkBeatmapCount(osu, 0);
Assert.IsEmpty(imported);
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
}
finally
{
host.Exit();
}
}
}
[Test] [Test]
public async Task TestRollbackOnFailure() public async Task TestRollbackOnFailure()
{ {
@ -506,7 +542,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await LoadOszIntoOsu(osu); var imported = await LoadOszIntoOsu(osu);
foreach (var b in imported.Beatmaps) foreach (var b in imported.Beatmaps)
b.OnlineBeatmapID = null; b.OnlineID = null;
osu.Dependencies.Get<BeatmapManager>().Update(imported); osu.Dependencies.Get<BeatmapManager>().Update(imported);
@ -545,19 +581,19 @@ namespace osu.Game.Tests.Beatmaps.IO
var toImport = new BeatmapSetInfo var toImport = new BeatmapSetInfo
{ {
OnlineBeatmapSetID = 1, OnlineID = 1,
Metadata = metadata, Metadata = metadata,
Beatmaps = new List<BeatmapInfo> Beatmaps = new List<BeatmapInfo>
{ {
new BeatmapInfo new BeatmapInfo
{ {
OnlineBeatmapID = 2, OnlineID = 2,
Metadata = metadata, Metadata = metadata,
BaseDifficulty = difficulty BaseDifficulty = difficulty
}, },
new BeatmapInfo new BeatmapInfo
{ {
OnlineBeatmapID = 2, OnlineID = 2,
Metadata = metadata, Metadata = metadata,
Status = BeatmapSetOnlineStatus.Loved, Status = BeatmapSetOnlineStatus.Loved,
BaseDifficulty = difficulty BaseDifficulty = difficulty
@ -570,8 +606,8 @@ namespace osu.Game.Tests.Beatmaps.IO
var imported = await manager.Import(toImport); var imported = await manager.Import(toImport);
Assert.NotNull(imported); Assert.NotNull(imported);
Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID); Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineID);
Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID); Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineID);
} }
finally finally
{ {
@ -1020,13 +1056,13 @@ namespace osu.Game.Tests.Beatmaps.IO
{ {
IEnumerable<BeatmapSetInfo> resultSets = null; IEnumerable<BeatmapSetInfo> resultSets = null;
var store = osu.Dependencies.Get<BeatmapManager>(); var store = osu.Dependencies.Get<BeatmapManager>();
waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineID == 241526)).Any(),
@"BeatmapSet did not import to the database in allocated time.", timeout); @"BeatmapSet did not import to the database in allocated time.", timeout);
// ensure we were stored to beatmap database backing... // ensure we were stored to beatmap database backing...
Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1).");
IEnumerable<BeatmapInfo> queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); IEnumerable<BeatmapInfo> queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526 && s.BaseDifficultyID > 0);
IEnumerable<BeatmapSetInfo> queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526); IEnumerable<BeatmapSetInfo> queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526);
// if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet.
waitForOrAssert(() => queryBeatmaps().Count() == 12, waitForOrAssert(() => queryBeatmaps().Count() == 12,
@ -1042,7 +1078,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var set = queryBeatmapSets().First(); var set = queryBeatmapSets().First();
foreach (BeatmapInfo b in set.Beatmaps) foreach (BeatmapInfo b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
Assert.IsTrue(set.Beatmaps.Count > 0); Assert.IsTrue(set.Beatmaps.Count > 0);
var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap;
Assert.IsTrue(beatmap?.HitObjects.Any() == true); Assert.IsTrue(beatmap?.HitObjects.Any() == true);

View File

@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var meta = beatmap.Metadata; var meta = beatmap.Metadata;
Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID);
Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.Artist);
Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("Soleily", meta.ArtistUnicode);
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile);

View File

@ -16,6 +16,7 @@ using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Models; using osu.Game.Models;
using osu.Game.Overlays.Notifications;
using osu.Game.Stores; using osu.Game.Stores;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using Realms; using Realms;
@ -367,6 +368,34 @@ namespace osu.Game.Tests.Database
}); });
} }
[Test]
public void TestModelCreationFailureDoesntReturn()
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
using var importer = new BeatmapImporter(realmFactory, storage);
using var store = new RealmRulesetStore(realmFactory, storage);
var progressNotification = new ImportProgressNotification();
var zipStream = new MemoryStream();
using (var zip = ZipArchive.Create())
zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate));
var imported = await importer.Import(
progressNotification,
new ImportTask(zipStream, string.Empty)
);
checkBeatmapSetCount(realmFactory.Context, 0);
checkBeatmapCount(realmFactory.Context, 0);
Assert.IsEmpty(imported);
Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State);
});
}
[Test] [Test]
public void TestRollbackOnFailure() public void TestRollbackOnFailure()
{ {

View File

@ -10,19 +10,28 @@ using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Users; using osu.Game.Users;
#nullable enable
namespace osu.Game.Tests.Models namespace osu.Game.Tests.Models
{ {
[TestFixture] [TestFixture]
public class DisplayStringTest public class DisplayStringTest
{ {
[Test]
public void TestNull()
{
IBeatmapSetInfo? beatmap = null;
Assert.That(beatmap.GetDisplayString(), Is.EqualTo("null"));
}
[Test] [Test]
public void TestBeatmapSet() public void TestBeatmapSet()
{ {
var mock = new Mock<IBeatmapSetInfo>(); var mock = new Mock<IBeatmapSetInfo>();
mock.Setup(m => m.Metadata.Artist).Returns("artist"); mock.Setup(m => m.Metadata!.Artist).Returns("artist");
mock.Setup(m => m.Metadata.Title).Returns("title"); mock.Setup(m => m.Metadata!.Title).Returns("title");
mock.Setup(m => m.Metadata.Author.Username).Returns("author"); mock.Setup(m => m.Metadata!.Author.Username).Returns("author");
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)"));
} }
@ -32,9 +41,9 @@ namespace osu.Game.Tests.Models
{ {
var mock = new Mock<IBeatmapSetInfo>(); var mock = new Mock<IBeatmapSetInfo>();
mock.Setup(m => m.Metadata.Artist).Returns("artist"); mock.Setup(m => m.Metadata!.Artist).Returns("artist");
mock.Setup(m => m.Metadata.Title).Returns("title"); mock.Setup(m => m.Metadata!.Title).Returns("title");
mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); mock.Setup(m => m.Metadata!.Author.Username).Returns(string.Empty);
Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title")); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title"));
} }

View File

@ -12,8 +12,8 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestOnlineWithOnline() public void TestOnlineWithOnline()
{ {
var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; var ourInfo = new BeatmapSetInfo { OnlineID = 123 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; var otherInfo = new BeatmapSetInfo { OnlineID = 123 };
Assert.AreEqual(ourInfo, otherInfo); Assert.AreEqual(ourInfo, otherInfo);
} }
@ -30,8 +30,8 @@ namespace osu.Game.Tests.NonVisual
[Test] [Test]
public void TestDatabasedWithOnline() public void TestDatabasedWithOnline()
{ {
var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 }; var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 }; var otherInfo = new BeatmapSetInfo { OnlineID = 12 };
Assert.AreEqual(ourInfo, otherInfo); Assert.AreEqual(ourInfo, otherInfo);
} }

View File

@ -207,8 +207,8 @@ namespace osu.Game.Tests.NonVisual.Filtering
public void TestCriteriaMatchingBeatmapIDs(string query, bool filtered) public void TestCriteriaMatchingBeatmapIDs(string query, bool filtered)
{ {
var beatmap = getExampleBeatmap(); var beatmap = getExampleBeatmap();
beatmap.OnlineBeatmapID = 20201010; beatmap.OnlineID = 20201010;
beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = 1535 }; beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 };
var criteria = new FilterCriteria { SearchText = query }; var criteria = new FilterCriteria { SearchText = query };
var carouselItem = new CarouselBeatmap(beatmap); var carouselItem = new CarouselBeatmap(beatmap);

View File

@ -22,7 +22,8 @@ namespace osu.Game.Tests.NonVisual.Ranking
var unstableRate = new UnstableRate(events); var unstableRate = new UnstableRate(events);
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10))); Assert.IsNotNull(unstableRate.Value);
Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10)));
} }
[Test] [Test]

View File

@ -19,7 +19,7 @@ namespace osu.Game.Tests.Online
private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo
{ {
OnlineBeatmapSetID = 1, OnlineID = 1,
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
Artist = "test author", Artist = "test author",

View File

@ -58,7 +58,7 @@ namespace osu.Game.Tests.Online
testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile);
testBeatmapSet = testBeatmapInfo.BeatmapSet; testBeatmapSet = testBeatmapInfo.BeatmapSet;
var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID); var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID);
if (existing != null) if (existing != null)
beatmaps.Delete(existing); beatmaps.Delete(existing);
@ -101,10 +101,10 @@ namespace osu.Game.Tests.Online
AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait());
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID)));
addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable);
} }

View File

@ -0,0 +1,123 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Game.Tests.Beatmaps.IO;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Editing
{
public class TestSceneEditorTestGameplay : EditorTestScene
{
protected override bool IsolateSavingFromDatabase => false;
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
[Resolved]
private OsuGameBase game { get; set; }
[Resolved]
private BeatmapManager beatmaps { get; set; }
private BeatmapSetInfo importedBeatmapSet;
public override void SetUpSteps()
{
AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result);
base.SetUpSteps();
}
protected override void LoadEditor()
{
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0));
base.LoadEditor();
}
[Test]
public void TestBasicGameplayTest()
{
AddStep("click test gameplay button", () =>
{
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
EditorPlayer editorPlayer = null;
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
AddStep("exit player", () => editorPlayer.Exit());
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
}
[Test]
public void TestCancelGameplayTestWithUnsavedChanges()
{
AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList()));
AddStep("click test gameplay button", () =>
{
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog);
AddStep("dismiss prompt", () =>
{
var button = DialogOverlay.CurrentDialog.Buttons.Last();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
AddWaitStep("wait some", 3);
AddAssert("stayed in editor", () => Stack.CurrentScreen is Editor);
}
[Test]
public void TestSaveChangesBeforeGameplayTest()
{
AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList()));
// bit of a hack to ensure this test can be ran multiple times without running into UNIQUE constraint failures
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = Guid.NewGuid().ToString());
AddStep("click test gameplay button", () =>
{
var button = Editor.ChildrenOfType<TestGameplayButton>().Single();
InputManager.MoveMouseTo(button);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog);
AddStep("save changes", () => DialogOverlay.CurrentDialog.PerformOkAction());
EditorPlayer editorPlayer = null;
AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null);
AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1);
AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Editor);
AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning);
}
public override void TearDownSteps()
{
base.TearDownSteps();
AddStep("delete imported", () =>
{
beatmaps.Delete(importedBeatmapSet);
});
}
}
}

View File

@ -4,6 +4,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing
[Cached(typeof(IBeatSnapProvider))] [Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap; private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
public TestSceneSetupScreen() public TestSceneSetupScreen()
{ {
editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap = new EditorBeatmap(new OsuBeatmap());

View File

@ -4,6 +4,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
@ -18,6 +19,9 @@ namespace osu.Game.Tests.Visual.Editing
[Cached(typeof(IBeatSnapProvider))] [Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap; private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
protected override bool ScrollUsingMouseWheel => false; protected override bool ScrollUsingMouseWheel => false;
public TestSceneTimingScreen() public TestSceneTimingScreen()

View File

@ -1,9 +1,10 @@
// 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;
using System.Diagnostics; using System.Diagnostics;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -20,16 +21,17 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestScenePerformancePointsCounter : OsuTestScene public class TestScenePerformancePointsCounter : OsuTestScene
{ {
[Cached] private DependencyProvidingContainer dependencyContainer;
private GameplayState gameplayState;
[Cached] private GameplayState gameplayState;
private ScoreProcessor scoreProcessor; private ScoreProcessor scoreProcessor;
private int iteration; private int iteration;
private Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
private PerformancePointsCounter counter; private PerformancePointsCounter counter;
public TestScenePerformancePointsCounter() [SetUpSteps]
public void SetUpSteps() => AddStep("create components", () =>
{ {
var ruleset = CreateRuleset(); var ruleset = CreateRuleset();
@ -38,32 +40,43 @@ namespace osu.Game.Tests.Visual.Gameplay
var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo) var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo)
.GetPlayableBeatmap(ruleset.RulesetInfo); .GetPlayableBeatmap(ruleset.RulesetInfo);
lastJudgementResult = new Bindable<JudgementResult>();
gameplayState = new GameplayState(beatmap, ruleset); gameplayState = new GameplayState(beatmap, ruleset);
gameplayState.LastJudgementResult.BindTo(lastJudgementResult);
scoreProcessor = new ScoreProcessor(); scoreProcessor = new ScoreProcessor();
}
Child = dependencyContainer = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(GameplayState), gameplayState),
(typeof(ScoreProcessor), scoreProcessor)
}
};
iteration = 0;
});
protected override Ruleset CreateRuleset() => new OsuRuleset(); protected override Ruleset CreateRuleset() => new OsuRuleset();
[SetUpSteps] private void createCounter() => AddStep("Create counter", () =>
public void SetUpSteps()
{ {
AddStep("Create counter", () => dependencyContainer.Child = counter = new PerformancePointsCounter
{ {
iteration = 0; Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = counter = new PerformancePointsCounter Scale = new Vector2(5),
{ };
Anchor = Anchor.Centre, });
Origin = Anchor.Centre,
Scale = new Vector2(5),
};
});
}
[Test] [Test]
public void TestBasicCounting() public void TestBasicCounting()
{ {
int previousValue = 0; int previousValue = 0;
createCounter();
AddAssert("counter displaying zero", () => counter.Current.Value == 0); AddAssert("counter displaying zero", () => counter.Current.Value == 0);
@ -86,6 +99,17 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("counter non-zero", () => counter.Current.Value > 0); AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
} }
[Test]
public void TestCounterUpdatesWithJudgementsBeforeCreation()
{
AddRepeatStep("Add judgement", applyOneJudgement, 10);
createCounter();
AddUntilStep("counter non-zero", () => counter.Current.Value > 0);
AddUntilStep("counter opaque", () => counter.Child.Alpha == 1);
}
private void applyOneJudgement() private void applyOneJudgement()
{ {
var scoreInfo = gameplayState.Score.ScoreInfo; var scoreInfo = gameplayState.Score.ScoreInfo;
@ -94,13 +118,14 @@ namespace osu.Game.Tests.Visual.Gameplay
scoreInfo.Accuracy = 1; scoreInfo.Accuracy = 1;
scoreInfo.Statistics[HitResult.Great] = iteration * 1000; scoreInfo.Statistics[HitResult.Great] = iteration * 1000;
scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject lastJudgementResult.Value = new OsuJudgementResult(new HitObject
{ {
StartTime = iteration * 10000, StartTime = iteration * 10000,
}, new OsuJudgement()) }, new OsuJudgement())
{ {
Type = HitResult.Perfect, Type = HitResult.Perfect,
}); };
scoreProcessor.ApplyResult(lastJudgementResult.Value);
iteration++; iteration++;
} }

View File

@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay
createPlayerTest(false, r => createPlayerTest(false, r =>
{ {
var beatmap = createTestBeatmap(r); var beatmap = createTestBeatmap(r);
beatmap.BeatmapInfo.OnlineBeatmapID = null; beatmap.BeatmapInfo.OnlineID = null;
return beatmap; return beatmap;
}); });

View File

@ -61,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).Result;
importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID ?? -1; importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1;
}); });
} }

View File

@ -61,6 +61,16 @@ namespace osu.Game.Tests.Visual.Gameplay
AddRepeatStep("Bring UR to 100", () => applyJudgement(-10, false), 10); AddRepeatStep("Bring UR to 100", () => applyJudgement(-10, false), 10);
} }
[Test]
public void TestCounterReceivesJudgementsBeforeCreation()
{
AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4);
AddStep("Create Display", recreateDisplay);
AddUntilStep("UR = 250", () => counter.Current.Value == 250.0);
}
private void recreateDisplay() private void recreateDisplay()
{ {
Clear(); Clear();

View File

@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
{ {
importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result;
importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0);
importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1; importedBeatmapId = importedBeatmap.OnlineID ?? -1;
} }
[SetUp] [SetUp]

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
foreach (int user in users) foreach (int user in users)
{ {
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0);
multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true)); multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true));
} }

View File

@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
foreach (int user in users) foreach (int user in users)
{ {
SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0);
var roomUser = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true); var roomUser = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true);
roomUser.MatchState = new TeamVersusUserState roomUser.MatchState = new TeamVersusUserState

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
beatmaps.Add(new BeatmapInfo beatmaps.Add(new BeatmapInfo
{ {
Ruleset = rulesets.GetRuleset(i % 4), Ruleset = rulesets.GetRuleset(i % 4),
OnlineBeatmapID = beatmapId, OnlineID = beatmapId,
Length = length, Length = length,
BPM = bpm, BPM = bpm,
BaseDifficulty = new BeatmapDifficulty() BaseDifficulty = new BeatmapDifficulty()
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
manager.Import(new BeatmapSetInfo manager.Import(new BeatmapSetInfo
{ {
OnlineBeatmapSetID = 10, OnlineID = 10,
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {

View File

@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
beatmaps.Add(new BeatmapInfo beatmaps.Add(new BeatmapInfo
{ {
Ruleset = new OsuRuleset().RulesetInfo, Ruleset = new OsuRuleset().RulesetInfo,
OnlineBeatmapID = beatmapId, OnlineID = beatmapId,
DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
Length = length, Length = length,
BPM = bpm, BPM = bpm,
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
manager.Import(new BeatmapSetInfo manager.Import(new BeatmapSetInfo
{ {
OnlineBeatmapSetID = 10, OnlineID = 10,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {

View File

@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Navigation
{ {
AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait());
PushAndConfirm(() => new TestPlaySongSelect()); PushAndConfirm(() => new TestPlaySongSelect());
AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == 241526); AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526);
} }
public class DialogBlockingScreen : OsuScreen public class DialogBlockingScreen : OsuScreen

View File

@ -107,20 +107,20 @@ namespace osu.Game.Tests.Visual.Navigation
imported = Game.BeatmapManager.Import(new BeatmapSetInfo imported = Game.BeatmapManager.Import(new BeatmapSetInfo
{ {
Hash = Guid.NewGuid().ToString(), Hash = Guid.NewGuid().ToString(),
OnlineBeatmapSetID = i, OnlineID = i,
Metadata = metadata, Metadata = metadata,
Beatmaps = new List<BeatmapInfo> Beatmaps = new List<BeatmapInfo>
{ {
new BeatmapInfo new BeatmapInfo
{ {
OnlineBeatmapID = i * 1024, OnlineID = i * 1024,
Metadata = metadata, Metadata = metadata,
BaseDifficulty = difficulty, BaseDifficulty = difficulty,
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
}, },
new BeatmapInfo new BeatmapInfo
{ {
OnlineBeatmapID = i * 2048, OnlineID = i * 2048,
Metadata = metadata, Metadata = metadata,
BaseDifficulty = difficulty, BaseDifficulty = difficulty,
Ruleset = ruleset ?? new OsuRuleset().RulesetInfo Ruleset = ruleset ?? new OsuRuleset().RulesetInfo
@ -145,11 +145,11 @@ namespace osu.Game.Tests.Visual.Navigation
private void presentSecondDifficultyAndConfirm(Func<BeatmapSetInfo> getImport, int importedID) private void presentSecondDifficultyAndConfirm(Func<BeatmapSetInfo> getImport, int importedID)
{ {
Predicate<BeatmapInfo> pred = b => b.OnlineBeatmapID == importedID * 2048; Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 2048;
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred)); AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == importedID * 2048); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID == importedID * 2048);
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID);
} }
} }

View File

@ -39,20 +39,20 @@ namespace osu.Game.Tests.Visual.Navigation
beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo
{ {
Hash = Guid.NewGuid().ToString(), Hash = Guid.NewGuid().ToString(),
OnlineBeatmapSetID = 1, OnlineID = 1,
Metadata = metadata, Metadata = metadata,
Beatmaps = new List<BeatmapInfo> Beatmaps = new List<BeatmapInfo>
{ {
new BeatmapInfo new BeatmapInfo
{ {
OnlineBeatmapID = 1 * 1024, OnlineID = 1 * 1024,
Metadata = metadata, Metadata = metadata,
BaseDifficulty = difficulty, BaseDifficulty = difficulty,
Ruleset = new OsuRuleset().RulesetInfo Ruleset = new OsuRuleset().RulesetInfo
}, },
new BeatmapInfo new BeatmapInfo
{ {
OnlineBeatmapID = 1 * 2048, OnlineID = 1 * 2048,
Metadata = metadata, Metadata = metadata,
BaseDifficulty = difficulty, BaseDifficulty = difficulty,
Ruleset = new OsuRuleset().RulesetInfo Ruleset = new OsuRuleset().RulesetInfo

View File

@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded);
AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()));
AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineID == 241526));
AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable);
createButtonWithBeatmap(createSoleily()); createButtonWithBeatmap(createSoleily());
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online
{ {
AddStep("remove soleily", () => AddStep("remove soleily", () =>
{ {
var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526);
if (beatmap != null) beatmaps.Delete(beatmap); if (beatmap != null) beatmaps.Delete(beatmap);
}); });

View File

@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
{ {
BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null } BeatmapInfo = { OnlineID = hasOnlineId ? 1234 : (int?)null }
}); });
AddStep("Run command", () => Add(new NowPlayingCommand())); AddStep("Run command", () => Add(new NowPlayingCommand()));

View File

@ -121,8 +121,8 @@ namespace osu.Game.Tests.Visual.Playlists
beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1;
// intentionally increment online IDs to clash with import below. // intentionally increment online IDs to clash with import below.
beatmap.BeatmapInfo.OnlineBeatmapID++; beatmap.BeatmapInfo.OnlineID++;
beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID++; beatmap.BeatmapInfo.BeatmapSet.OnlineID++;
importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value;
}); });

View File

@ -337,7 +337,7 @@ namespace osu.Game.Tests.Visual.Ranking
public UnrankedSoloResultsScreen(ScoreInfo score) public UnrankedSoloResultsScreen(ScoreInfo score)
: base(score, true) : base(score, true)
{ {
Score.BeatmapInfo.OnlineBeatmapID = 0; Score.BeatmapInfo.OnlineID = 0;
Score.BeatmapInfo.Status = BeatmapSetOnlineStatus.Pending; Score.BeatmapInfo.Status = BeatmapSetOnlineStatus.Pending;
} }

View File

@ -838,7 +838,7 @@ namespace osu.Game.Tests.Visual.SongSelect
return new BeatmapSetInfo return new BeatmapSetInfo
{ {
ID = id, ID = id,
OnlineBeatmapSetID = id, OnlineID = id,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
@ -867,7 +867,7 @@ namespace osu.Game.Tests.Visual.SongSelect
yield return new BeatmapInfo yield return new BeatmapInfo
{ {
OnlineBeatmapID = id++ * 10, OnlineID = id++ * 10,
DifficultyName = version, DifficultyName = version,
StarRating = diff, StarRating = diff,
Ruleset = new OsuRuleset().RulesetInfo, Ruleset = new OsuRuleset().RulesetInfo,
@ -884,7 +884,7 @@ namespace osu.Game.Tests.Visual.SongSelect
var toReturn = new BeatmapSetInfo var toReturn = new BeatmapSetInfo
{ {
ID = id, ID = id,
OnlineBeatmapSetID = id, OnlineID = id,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {
@ -900,7 +900,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
toReturn.Beatmaps.Add(new BeatmapInfo toReturn.Beatmaps.Add(new BeatmapInfo
{ {
OnlineBeatmapID = b * 10, OnlineID = b * 10,
Path = $"extra{b}.osu", Path = $"extra{b}.osu",
DifficultyName = $"Extra {b}", DifficultyName = $"Extra {b}",
Ruleset = rulesets.GetRuleset((b - 1) % 4), Ruleset = rulesets.GetRuleset((b - 1) % 4),

View File

@ -388,7 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
leaderboard.BeatmapInfo = new BeatmapInfo leaderboard.BeatmapInfo = new BeatmapInfo
{ {
OnlineBeatmapID = 1113057, OnlineID = 1113057,
Status = status, Status = status,
}; };
} }

View File

@ -180,11 +180,11 @@ namespace osu.Game.Tests.Visual.SongSelect
var beatmapSet = new BeatmapSetInfo var beatmapSet = new BeatmapSetInfo
{ {
Hash = Guid.NewGuid().ToString(), Hash = Guid.NewGuid().ToString(),
OnlineBeatmapSetID = importID, OnlineID = importID,
Metadata = metadata, Metadata = metadata,
Beatmaps = difficultyRulesets.Select((ruleset, difficultyIndex) => new BeatmapInfo Beatmaps = difficultyRulesets.Select((ruleset, difficultyIndex) => new BeatmapInfo
{ {
OnlineBeatmapID = importID * 1024 + difficultyIndex, OnlineID = importID * 1024 + difficultyIndex,
Metadata = metadata, Metadata = metadata,
BaseDifficulty = new BeatmapDifficulty(), BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset, Ruleset = ruleset,
@ -205,8 +205,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("recommended beatmap displayed", () => AddUntilStep("recommended beatmap displayed", () =>
{ {
int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineBeatmapID; int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineID;
return Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == expectedID; return Game.Beatmap.Value.BeatmapInfo.OnlineID == expectedID;
}); });
} }
} }

View File

@ -507,13 +507,13 @@ namespace osu.Game.Tests.Visual.SongSelect
i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0); i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0);
}); });
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID);
AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty); AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = string.Empty);
AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineID == target.OnlineID);
AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineID == target.OnlineID);
} }
[Test] [Test]
@ -544,8 +544,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null);
AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID);
AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID);
AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nononoo"); AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType<SearchTextBox>().First().Text = "nononoo");
@ -918,7 +918,7 @@ namespace osu.Game.Tests.Visual.SongSelect
beatmaps.Add(new BeatmapInfo beatmaps.Add(new BeatmapInfo
{ {
Ruleset = getRuleset(), Ruleset = getRuleset(),
OnlineBeatmapID = beatmapId, OnlineID = beatmapId,
DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
Length = length, Length = length,
BPM = bpm, BPM = bpm,
@ -931,7 +931,7 @@ namespace osu.Game.Tests.Visual.SongSelect
return new BeatmapSetInfo return new BeatmapSetInfo
{ {
OnlineBeatmapSetID = setId, OnlineID = setId,
Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(),
Metadata = new BeatmapMetadata Metadata = new BeatmapMetadata
{ {

View File

@ -290,7 +290,7 @@ namespace osu.Game.Beatmaps
catch (BeatmapInvalidForRulesetException e) catch (BeatmapInvalidForRulesetException e)
{ {
if (rulesetInfo.Equals(beatmapInfo.Ruleset)) if (rulesetInfo.Equals(beatmapInfo.Ruleset))
Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineBeatmapID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset}).");
return new StarDifficulty(); return new StarDifficulty();
} }

View File

@ -23,13 +23,14 @@ namespace osu.Game.Beatmaps
public int BeatmapVersion; public int BeatmapVersion;
private int? onlineBeatmapID; private int? onlineID;
[JsonProperty("id")] [JsonProperty("id")]
public int? OnlineBeatmapID [Column("OnlineBeatmapID")]
public int? OnlineID
{ {
get => onlineBeatmapID; get => onlineID;
set => onlineBeatmapID = value > 0 ? value : null; set => onlineID = value > 0 ? value : null;
} }
[JsonIgnore] [JsonIgnore]
@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps
#region Implementation of IHasOnlineID #region Implementation of IHasOnlineID
public int OnlineID => OnlineBeatmapID ?? -1; int IHasOnlineID<int>.OnlineID => OnlineID ?? -1;
#endregion #endregion

View File

@ -94,17 +94,17 @@ namespace osu.Game.Beatmaps
validateOnlineIds(beatmapSet); validateOnlineIds(beatmapSet);
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0);
if (OnlineLookupQueue != null) if (OnlineLookupQueue != null)
await OnlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false); await OnlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false);
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0))
{ {
if (beatmapSet.OnlineBeatmapSetID != null) if (beatmapSet.OnlineID != null)
{ {
beatmapSet.OnlineBeatmapSetID = null; beatmapSet.OnlineID = null;
LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs"); LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs");
} }
} }
@ -116,27 +116,27 @@ namespace osu.Game.Beatmaps
throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}.");
// check if a set already exists with the same online id, delete if it does. // check if a set already exists with the same online id, delete if it does.
if (beatmapSet.OnlineBeatmapSetID != null) if (beatmapSet.OnlineID != null)
{ {
var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineID == beatmapSet.OnlineID);
if (existingSetWithSameOnlineID != null) if (existingSetWithSameOnlineID != null)
{ {
Delete(existingSetWithSameOnlineID); Delete(existingSetWithSameOnlineID);
// in order to avoid a unique key constraint, immediately remove the online ID from the previous set. // in order to avoid a unique key constraint, immediately remove the online ID from the previous set.
existingSetWithSameOnlineID.OnlineBeatmapSetID = null; existingSetWithSameOnlineID.OnlineID = null;
foreach (var b in existingSetWithSameOnlineID.Beatmaps) foreach (var b in existingSetWithSameOnlineID.Beatmaps)
b.OnlineBeatmapID = null; b.OnlineID = null;
LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineID}). It has been deleted.");
} }
} }
} }
private void validateOnlineIds(BeatmapSetInfo beatmapSet) private void validateOnlineIds(BeatmapSetInfo beatmapSet)
{ {
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID.HasValue).Select(b => b.OnlineID).ToList();
// ensure all IDs are unique // ensure all IDs are unique
if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1))
@ -147,7 +147,7 @@ namespace osu.Game.Beatmaps
} }
// find any existing beatmaps in the database that have matching online ids // find any existing beatmaps in the database that have matching online ids
var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList(); var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineID)).ToList();
if (existingBeatmaps.Count > 0) if (existingBeatmaps.Count > 0)
{ {
@ -162,7 +162,7 @@ namespace osu.Game.Beatmaps
} }
} }
void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = null);
} }
/// <summary> /// <summary>
@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps
if (!base.CanSkipImport(existing, import)) if (!base.CanSkipImport(existing, import))
return false; return false;
return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); return existing.Beatmaps.Any(b => b.OnlineID != null);
} }
protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import)
@ -250,11 +250,11 @@ namespace osu.Game.Beatmaps
if (!base.CanReuseExisting(existing, import)) if (!base.CanReuseExisting(existing, import))
return false; return false;
var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); var existingIds = existing.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i);
var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); var importIds = import.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i);
// force re-import if we are not in a sane state. // force re-import if we are not in a sane state.
return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds);
} }
/// <summary> /// <summary>
@ -349,7 +349,7 @@ namespace osu.Game.Beatmaps
protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable<BeatmapSetInfo> items) protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable<BeatmapSetInfo> items)
=> base.CheckLocalAvailability(model, items) => base.CheckLocalAvailability(model, items)
|| (model.OnlineBeatmapSetID != null && items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID)); || (model.OnlineID != null && items.Any(b => b.OnlineID == model.OnlineID));
protected override BeatmapSetInfo CreateModel(ArchiveReader reader) protected override BeatmapSetInfo CreateModel(ArchiveReader reader)
{ {
@ -368,7 +368,7 @@ namespace osu.Game.Beatmaps
return new BeatmapSetInfo return new BeatmapSetInfo
{ {
OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID,
Beatmaps = new List<BeatmapInfo>(), Beatmaps = new List<BeatmapInfo>(),
Metadata = beatmap.Metadata, Metadata = beatmap.Metadata,
DateAdded = DateTimeOffset.UtcNow DateAdded = DateTimeOffset.UtcNow

View File

@ -84,8 +84,8 @@ namespace osu.Game.Beatmaps
{ {
beatmapInfo.Status = res.Status; beatmapInfo.Status = res.Status;
beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None; beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None;
beatmapInfo.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID;
beatmapInfo.OnlineBeatmapID = res.OnlineID; beatmapInfo.OnlineID = res.OnlineID;
if (beatmapInfo.Metadata != null) if (beatmapInfo.Metadata != null)
beatmapInfo.Metadata.AuthorID = res.AuthorID; beatmapInfo.Metadata.AuthorID = res.AuthorID;
@ -103,7 +103,7 @@ namespace osu.Game.Beatmaps
void fail(Exception e) void fail(Exception e)
{ {
beatmapInfo.OnlineBeatmapID = null; beatmapInfo.OnlineID = null;
logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})");
} }
} }
@ -161,7 +161,7 @@ namespace osu.Game.Beatmaps
if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) if (string.IsNullOrEmpty(beatmapInfo.MD5Hash)
&& string.IsNullOrEmpty(beatmapInfo.Path) && string.IsNullOrEmpty(beatmapInfo.Path)
&& beatmapInfo.OnlineBeatmapID == null) && beatmapInfo.OnlineID == null)
return false; return false;
try try
@ -172,10 +172,10 @@ namespace osu.Game.Beatmaps
using (var cmd = db.CreateCommand()) using (var cmd = db.CreateCommand())
{ {
cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path";
cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash));
cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmapInfo.OnlineBeatmapID ?? (object)DBNull.Value)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID ?? (object)DBNull.Value));
cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path)); cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path));
using (var reader = cmd.ExecuteReader()) using (var reader = cmd.ExecuteReader())
@ -186,8 +186,8 @@ namespace osu.Game.Beatmaps
beatmapInfo.Status = status; beatmapInfo.Status = status;
beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.Status = status;
beatmapInfo.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0);
beatmapInfo.OnlineBeatmapID = reader.GetInt32(1); beatmapInfo.OnlineID = reader.GetInt32(1);
if (beatmapInfo.Metadata != null) if (beatmapInfo.Metadata != null)
beatmapInfo.Metadata.AuthorID = reader.GetInt32(3); beatmapInfo.Metadata.AuthorID = reader.GetInt32(3);

View File

@ -16,12 +16,13 @@ namespace osu.Game.Beatmaps
{ {
public int ID { get; set; } public int ID { get; set; }
private int? onlineBeatmapSetID; private int? onlineID;
public int? OnlineBeatmapSetID [Column("OnlineBeatmapSetID")]
public int? OnlineID
{ {
get => onlineBeatmapSetID; get => onlineID;
set => onlineBeatmapSetID = value > 0 ? value : null; set => onlineID = value > 0 ? value : null;
} }
public DateTimeOffset DateAdded { get; set; } public DateTimeOffset DateAdded { get; set; }
@ -74,8 +75,8 @@ namespace osu.Game.Beatmaps
if (ID != 0 && other.ID != 0) if (ID != 0 && other.ID != 0)
return ID == other.ID; return ID == other.ID;
if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue) if (OnlineID.HasValue && other.OnlineID.HasValue)
return OnlineBeatmapSetID == other.OnlineBeatmapSetID; return OnlineID == other.OnlineID;
if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash))
return Hash == other.Hash; return Hash == other.Hash;
@ -85,7 +86,7 @@ namespace osu.Game.Beatmaps
#region Implementation of IHasOnlineID #region Implementation of IHasOnlineID
public int OnlineID => OnlineBeatmapSetID ?? -1; int IHasOnlineID<int>.OnlineID => OnlineID ?? -1;
#endregion #endregion

View File

@ -263,11 +263,11 @@ namespace osu.Game.Beatmaps.Formats
break; break;
case @"BeatmapID": case @"BeatmapID":
beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); beatmap.BeatmapInfo.OnlineID = Parsing.ParseInt(pair.Value);
break; break;
case @"BeatmapSetID": case @"BeatmapSetID":
beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineID = Parsing.ParseInt(pair.Value) };
break; break;
} }
} }

View File

@ -133,8 +133,8 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}")); writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}")); if (beatmap.BeatmapInfo.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineID}"));
if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}")); if (beatmap.BeatmapInfo.BeatmapSet?.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineID}"));
} }
private void handleDifficulty(TextWriter writer) private void handleDifficulty(TextWriter writer)

View File

@ -264,7 +264,7 @@ namespace osu.Game.Database
model = CreateModel(archive); model = CreateModel(archive);
if (model == null) if (model == null)
return Task.FromResult<ILive<TModel>>(new EntityFrameworkLive<TModel>(null)); return Task.FromResult<ILive<TModel>>(null);
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {

View File

@ -7,6 +7,8 @@ using System.Threading.Tasks;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
#nullable enable
namespace osu.Game.Database namespace osu.Game.Database
{ {
/// <summary> /// <summary>
@ -26,7 +28,7 @@ namespace osu.Game.Database
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
/// <returns>The imported model, if successful.</returns> /// <returns>The imported model, if successful.</returns>
Task<ILive<TModel>> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); Task<ILive<TModel>?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Silently import an item from an <see cref="ArchiveReader"/>. /// Silently import an item from an <see cref="ArchiveReader"/>.
@ -34,7 +36,7 @@ namespace osu.Game.Database
/// <param name="archive">The archive to be imported.</param> /// <param name="archive">The archive to be imported.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
Task<ILive<TModel>> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); Task<ILive<TModel>?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Silently import an item from a <typeparamref name="TModel"/>. /// Silently import an item from a <typeparamref name="TModel"/>.
@ -43,7 +45,7 @@ namespace osu.Game.Database
/// <param name="archive">An optional archive to use for model population.</param> /// <param name="archive">An optional archive to use for model population.</param>
/// <param name="lowPriority">Whether this is a low priority import.</param> /// <param name="lowPriority">Whether this is a low priority import.</param>
/// <param name="cancellationToken">An optional cancellation token.</param> /// <param name="cancellationToken">An optional cancellation token.</param>
Task<ILive<TModel>> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); Task<ILive<TModel>?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// A user displayable name for the model type associated with this manager. /// A user displayable name for the model type associated with this manager.

View File

@ -47,10 +47,30 @@ namespace osu.Game.Database
/// </summary> /// </summary>
public ArchiveReader GetReader() public ArchiveReader GetReader()
{ {
if (Stream != null) return Stream != null
return new ZipArchiveReader(Stream, Path); ? getReaderFrom(Stream)
: getReaderFrom(Path);
}
return getReaderFrom(Path); /// <summary>
/// Creates an <see cref="ArchiveReader"/> from a stream.
/// </summary>
/// <param name="stream">A seekable stream containing the archive content.</param>
/// <returns>A reader giving access to the archive's content.</returns>
private ArchiveReader getReaderFrom(Stream stream)
{
if (!(stream is MemoryStream memoryStream))
{
// This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out).
byte[] buffer = new byte[stream.Length];
stream.Read(buffer, 0, (int)stream.Length);
memoryStream = new MemoryStream(buffer);
}
if (ZipUtils.IsZipArchive(memoryStream))
return new ZipArchiveReader(memoryStream, Path);
return new LegacyByteArrayReader(memoryStream.ToArray(), Path);
} }
/// <summary> /// <summary>

View File

@ -125,11 +125,11 @@ namespace osu.Game.Database
{ {
base.OnModelCreating(modelBuilder); base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.OnlineBeatmapID).IsUnique(); modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.OnlineID).IsUnique();
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.MD5Hash); modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.MD5Hash);
modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.Hash); modelBuilder.Entity<BeatmapInfo>().HasIndex(b => b.Hash);
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.OnlineBeatmapSetID).IsUnique(); modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.OnlineID).IsUnique();
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending); modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending);
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique();

View File

@ -54,7 +54,7 @@ namespace osu.Game.Extensions
} }
// fallback in case none of the above happens to match. // fallback in case none of the above happens to match.
result ??= model.ToString(); result ??= model?.ToString() ?? @"null";
return result; return result;
} }
} }

View File

@ -7,7 +7,7 @@ using System.IO;
namespace osu.Game.IO.Archives namespace osu.Game.IO.Archives
{ {
/// <summary> /// <summary>
/// Allows reading a single file from the provided stream. /// Allows reading a single file from the provided byte array.
/// </summary> /// </summary>
public class LegacyByteArrayReader : ArchiveReader public class LegacyByteArrayReader : ArchiveReader
{ {

View File

@ -76,6 +76,7 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
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),
}; };
public IEnumerable<KeyBinding> InGameKeyBindings => new[] public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@ -288,6 +289,9 @@ namespace osu.Game.Input.Bindings
ToggleChatFocus, ToggleChatFocus,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))]
EditorCycleGridDisplayMode EditorCycleGridDisplayMode,
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))]
EditorTestGameplay
} }
} }

View File

@ -169,6 +169,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode");
/// <summary>
/// "Test gameplay"
/// </summary>
public static LocalisableString EditorTestGameplay => new TranslatableString(getKey(@"editor_test_gameplay"), @"Test gameplay");
/// <summary> /// <summary>
/// "Hold for HUD" /// "Hold for HUD"
/// </summary> /// </summary>

View File

@ -29,7 +29,7 @@ namespace osu.Game.Online
return; return;
// Used to interact with manager classes that don't support interface types. Will eventually be replaced. // Used to interact with manager classes that don't support interface types. Will eventually be replaced.
var beatmapSetInfo = new BeatmapSetInfo { OnlineBeatmapSetID = TrackedItem.OnlineID }; var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID };
if (Manager.IsAvailableLocally(beatmapSetInfo)) if (Manager.IsAvailableLocally(beatmapSetInfo))
UpdateState(DownloadState.LocallyAvailable); UpdateState(DownloadState.LocallyAvailable);

View File

@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat
break; break;
} }
string beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString(); string beatmapString = beatmapInfo.OnlineID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfo}]" : beatmapInfo.ToString();
channelManager.PostMessage($"is {verb} {beatmapString}", true, target); channelManager.PostMessage($"is {verb} {beatmapString}", true, target);
Expire(); Expire();

View File

@ -113,7 +113,7 @@ namespace osu.Game.Online.Rooms
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
return beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null;
} }
} }
} }

View File

@ -144,7 +144,7 @@ namespace osu.Game.Online.Spectator
IsPlaying = true; IsPlaying = true;
// transfer state at point of beginning play // transfer state at point of beginning play
currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineBeatmapID; currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID;
currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.RulesetID = score.ScoreInfo.RulesetID;
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();

View File

@ -443,7 +443,7 @@ namespace osu.Game
BeatmapSetInfo databasedSet = null; BeatmapSetInfo databasedSet = null;
if (beatmap.OnlineID > 0) if (beatmap.OnlineID > 0)
databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineID); databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID);
if (beatmap is BeatmapSetInfo localBeatmap) if (beatmap is BeatmapSetInfo localBeatmap)
databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash);

View File

@ -80,7 +80,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
case DownloadState.LocallyAvailable: case DownloadState.LocallyAvailable:
Predicate<BeatmapInfo> findPredicate = null; Predicate<BeatmapInfo> findPredicate = null;
if (SelectedBeatmap.Value != null) if (SelectedBeatmap.Value != null)
findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineID; findPredicate = b => b.OnlineID == SelectedBeatmap.Value.OnlineID;
game?.PresentBeatmap(beatmapSet, findPredicate); game?.PresentBeatmap(beatmapSet, findPredicate);
break; break;

View File

@ -0,0 +1,37 @@
// 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;
namespace osu.Game.Rulesets.Scoring
{
public static class HitEventExtensions
{
/// <summary>
/// Calculates the "unstable rate" for a sequence of <see cref="HitEvent"/>s.
/// </summary>
/// <returns>
/// A non-null <see langword="double"/> value if unstable rate could be calculated,
/// and <see langword="null"/> if unstable rate cannot be calculated due to <paramref name="hitEvents"/> being empty.
/// </returns>
public static double? CalculateUnstableRate(this IEnumerable<HitEvent> hitEvents)
{
double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray();
return 10 * standardDeviation(timeOffsets);
}
private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit();
private static double? standardDeviation(double[] timeOffsets)
{
if (timeOffsets.Length == 0)
return null;
double mean = timeOffsets.Average();
double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
return Math.Sqrt(squares / timeOffsets.Length);
}
}
}

View File

@ -53,6 +53,12 @@ namespace osu.Game.Rulesets.Scoring
/// </summary> /// </summary>
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>(); public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>();
/// <summary>
/// The <see cref="HitEvent"/>s collected during gameplay thus far.
/// Intended for use with various statistics displays.
/// </summary>
public IReadOnlyList<HitEvent> HitEvents => hitEvents;
/// <summary> /// <summary>
/// The default portion of <see cref="max_score"/> awarded for hitting <see cref="HitObject"/>s accurately. Defaults to 30%. /// The default portion of <see cref="max_score"/> awarded for hitting <see cref="HitObject"/>s accurately. Defaults to 30%.
/// </summary> /// </summary>

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit.Components.Timelines.Summary
{
public class TestGameplayButton : OsuButton
{
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = OsuFont.TorusAlternate.With(weight: FontWeight.Light, size: 24),
Shadow = false
};
[BackgroundDependencyLoader]
private void load(OsuColour colours, OverlayColourProvider colourProvider)
{
BackgroundColour = colours.Orange1;
SpriteText.Colour = colourProvider.Background6;
Text = "Test!";
}
}
}

View File

@ -36,6 +36,7 @@ using osu.Game.Screens.Edit.Timing;
using osu.Game.Screens.Edit.Verify; using osu.Game.Screens.Edit.Verify;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Users; using osu.Game.Users;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -90,6 +91,8 @@ namespace osu.Game.Screens.Edit
private DependencyContainer dependencies; private DependencyContainer dependencies;
private TestGameplayButton testGameplayButton;
private bool isNewBeatmap; private bool isNewBeatmap;
protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo);
@ -106,6 +109,9 @@ namespace osu.Game.Screens.Edit
[Cached] [Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard(); public readonly EditorClipboard Clipboard = new EditorClipboard();
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
public Editor(EditorLoader loader = null) public Editor(EditorLoader loader = null)
{ {
this.loader = loader; this.loader = loader;
@ -262,7 +268,8 @@ namespace osu.Game.Screens.Edit
{ {
new Dimension(GridSizeMode.Absolute, 220), new Dimension(GridSizeMode.Absolute, 220),
new Dimension(), new Dimension(),
new Dimension(GridSizeMode.Absolute, 220) new Dimension(GridSizeMode.Absolute, 220),
new Dimension(GridSizeMode.Absolute, 120),
}, },
Content = new[] Content = new[]
{ {
@ -283,6 +290,13 @@ namespace osu.Game.Screens.Edit
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10 }, Padding = new MarginPadding { Left = 10 },
Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, Child = new PlaybackControl { RelativeSizeAxes = Axes.Both },
},
testGameplayButton = new TestGameplayButton
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 10 },
Size = new Vector2(1),
Action = testGameplay
} }
}, },
} }
@ -456,6 +470,10 @@ namespace osu.Game.Screens.Edit
menuBar.Mode.Value = EditorScreenMode.Verify; menuBar.Mode.Value = EditorScreenMode.Verify;
return true; return true;
case GlobalAction.EditorTestGameplay:
testGameplayButton.TriggerClick();
return true;
default: default:
return false; return false;
} }
@ -510,7 +528,21 @@ namespace osu.Game.Screens.Edit
ApplyToBackground(b => b.FadeColour(Color4.White, 500)); ApplyToBackground(b => b.FadeColour(Color4.White, 500));
resetTrack(); resetTrack();
// To update the game-wide beatmap with any changes, perform a re-fetch on exit. refetchBeatmap();
return base.OnExiting(next);
}
public override void OnSuspending(IScreen next)
{
refetchBeatmap();
base.OnSuspending(next);
}
private void refetchBeatmap()
{
// To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend.
// This is required as the editor makes its local changes via EditorBeatmap // This is required as the editor makes its local changes via EditorBeatmap
// (which are not propagated outwards to a potentially cached WorkingBeatmap). // (which are not propagated outwards to a potentially cached WorkingBeatmap).
var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo);
@ -520,8 +552,6 @@ namespace osu.Game.Screens.Edit
Logger.Log("Editor providing re-fetched beatmap post edit session"); Logger.Log("Editor providing re-fetched beatmap post edit session");
Beatmap.Value = refetchedBeatmap; Beatmap.Value = refetchedBeatmap;
} }
return base.OnExiting(next);
} }
private void confirmExitWithSave() private void confirmExitWithSave()
@ -752,6 +782,24 @@ namespace osu.Game.Screens.Edit
loader?.CancelPendingDifficultySwitch(); loader?.CancelPendingDifficultySwitch();
} }
private void testGameplay()
{
if (HasUnsavedChanges)
{
dialogOverlay.Push(new SaveBeforeGameplayTestDialog(() =>
{
Save();
pushEditorPlayer();
}));
}
else
{
pushEditorPlayer();
}
void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer()));
}
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime);

View File

@ -0,0 +1,44 @@
// 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.Screens;
using osu.Game.Overlays;
using osu.Game.Screens.Play;
namespace osu.Game.Screens.Edit
{
public class EditorPlayer : Player
{
public EditorPlayer()
: base(new PlayerConfiguration { ShowResults = false })
{
}
[Resolved]
private MusicController musicController { get; set; }
protected override void LoadComplete()
{
base.LoadComplete();
ScoreProcessor.HasCompleted.BindValueChanged(completed =>
{
if (completed.NewValue)
Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY);
});
}
protected override void PrepareReplay()
{
// don't record replays.
}
protected override bool CheckModsAllowFailure() => false; // never fail.
public override bool OnExiting(IScreen next)
{
musicController.Stop();
return base.OnExiting(next);
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OverlayColourProvider colourProvider)
{ {
base.Content.Add(new Container base.Content.Add(new Container
{ {
@ -41,7 +42,7 @@ namespace osu.Game.Screens.Edit
{ {
new Box new Box
{ {
Colour = ColourProvider.Background3, Colour = colourProvider.Background3,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
roundedContent = new Container roundedContent = new Container

View File

@ -6,7 +6,6 @@ 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.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
@ -18,9 +17,6 @@ namespace osu.Game.Screens.Edit
[Resolved] [Resolved]
protected EditorBeatmap EditorBeatmap { get; private set; } protected EditorBeatmap EditorBeatmap { get; private set; }
[Cached]
protected readonly OverlayColourProvider ColourProvider;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container content; private readonly Container content;
@ -34,8 +30,6 @@ namespace osu.Game.Screens.Edit
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both };
} }

View File

@ -0,0 +1,32 @@
// 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.Sprites;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Screens.Edit
{
public class SaveBeforeGameplayTestDialog : PopupDialog
{
public SaveBeforeGameplayTestDialog(Action saveAndPreview)
{
HeaderText = "The beatmap will be saved in order to test it.";
Icon = FontAwesome.Regular.Save;
Buttons = new PopupDialogButton[]
{
new PopupDialogOkButton
{
Text = "Sounds good, let's go!",
Action = saveAndPreview
},
new PopupDialogCancelButton
{
Text = "Oops, continue editing",
},
};
}
}
}

View File

@ -366,7 +366,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
var beatmap = SelectedItem.Value?.Beatmap.Value; var beatmap = SelectedItem.Value?.Beatmap.Value;
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineID); var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
} }

View File

@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private void load(IBindable<RulesetInfo> ruleset) private void load(IBindable<RulesetInfo> ruleset)
{ {
// Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem // Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem
if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineID) if (Beatmap.Value.BeatmapInfo.OnlineID != PlaylistItem.Beatmap.Value.OnlineID)
throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap");
if (ruleset.Value.ID != PlaylistItem.Ruleset.Value.ID) if (ruleset.Value.ID != PlaylistItem.Ruleset.Value.ID)

View File

@ -92,6 +92,9 @@ namespace osu.Game.Screens.Play.HUD
scoreProcessor.NewJudgement += onJudgementChanged; scoreProcessor.NewJudgement += onJudgementChanged;
scoreProcessor.JudgementReverted += onJudgementChanged; scoreProcessor.JudgementReverted += onJudgementChanged;
} }
if (gameplayState?.LastJudgementResult.Value != null)
onJudgementChanged(gameplayState.LastJudgementResult.Value);
} }
private bool isValid; private bool isValid;
@ -155,7 +158,10 @@ namespace osu.Game.Screens.Play.HUD
base.Dispose(isDisposing); base.Dispose(isDisposing);
if (scoreProcessor != null) if (scoreProcessor != null)
{
scoreProcessor.NewJudgement -= onJudgementChanged; scoreProcessor.NewJudgement -= onJudgementChanged;
scoreProcessor.JudgementReverted -= onJudgementChanged;
}
loadCancellationSource?.Cancel(); loadCancellationSource?.Cancel();
} }
@ -184,7 +190,7 @@ namespace osu.Game.Screens.Play.HUD
{ {
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 16) Font = OsuFont.Numeric.With(size: 16, fixedWidth: true)
}, },
new OsuSpriteText new OsuSpriteText
{ {

View File

@ -2,8 +2,6 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -17,6 +15,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
@ -29,8 +28,6 @@ namespace osu.Game.Screens.Play.HUD
private const float alpha_when_invalid = 0.3f; private const float alpha_when_invalid = 0.3f;
private readonly Bindable<bool> valid = new Bindable<bool>(); private readonly Bindable<bool> valid = new Bindable<bool>();
private readonly List<double> hitOffsets = new List<double>();
[Resolved] [Resolved]
private ScoreProcessor scoreProcessor { get; set; } private ScoreProcessor scoreProcessor { get; set; }
@ -54,41 +51,20 @@ namespace osu.Game.Screens.Play.HUD
{ {
base.LoadComplete(); base.LoadComplete();
scoreProcessor.NewJudgement += onJudgementAdded; scoreProcessor.NewJudgement += updateDisplay;
scoreProcessor.JudgementReverted += onJudgementReverted; scoreProcessor.JudgementReverted += updateDisplay;
}
private void onJudgementAdded(JudgementResult judgement)
{
if (!changesUnstableRate(judgement)) return;
hitOffsets.Add(judgement.TimeOffset);
updateDisplay(); updateDisplay();
} }
private void onJudgementReverted(JudgementResult judgement) private void updateDisplay(JudgementResult _) => Scheduler.AddOnce(updateDisplay);
{
if (judgement.FailedAtJudgement || !changesUnstableRate(judgement)) return;
hitOffsets.RemoveAt(hitOffsets.Count - 1);
updateDisplay();
}
private void updateDisplay() private void updateDisplay()
{ {
// At Count = 0, we get NaN, While we are allowing count = 1, it will be 0 since average = offset. double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate();
if (hitOffsets.Count > 0)
{ valid.Value = unstableRate != null;
double mean = hitOffsets.Average(); if (unstableRate != null)
double squares = hitOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); Current.Value = (int)Math.Round(unstableRate.Value);
Current.Value = (int)(Math.Sqrt(squares / hitOffsets.Count) * 10);
valid.Value = true;
}
else
{
Current.Value = 0;
valid.Value = false;
}
} }
protected override IHasText CreateText() => new TextComponent protected override IHasText CreateText() => new TextComponent
@ -102,8 +78,8 @@ namespace osu.Game.Screens.Play.HUD
if (scoreProcessor == null) return; if (scoreProcessor == null) return;
scoreProcessor.NewJudgement -= onJudgementAdded; scoreProcessor.NewJudgement -= updateDisplay;
scoreProcessor.JudgementReverted -= onJudgementReverted; scoreProcessor.JudgementReverted -= updateDisplay;
} }
private class TextComponent : CompositeDrawable, IHasText private class TextComponent : CompositeDrawable, IHasText
@ -123,6 +99,7 @@ namespace osu.Game.Screens.Play.HUD
InternalChild = new FillFlowContainer InternalChild = new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Spacing = new Vector2(2),
Children = new Drawable[] Children = new Drawable[]
{ {
text = new OsuSpriteText text = new OsuSpriteText
@ -136,8 +113,8 @@ namespace osu.Game.Screens.Play.HUD
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), Font = OsuFont.Numeric.With(size: 8, fixedWidth: true),
Text = "UR", Text = @"UR",
Padding = new MarginPadding { Bottom = 1.5f }, Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better
} }
} }
}; };

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using ManagedBass.Fx;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -67,6 +68,7 @@ namespace osu.Game.Screens.Play
private readonly BindableDouble volumeAdjustment = new BindableDouble(1); private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
private AudioFilter lowPassFilter; private AudioFilter lowPassFilter;
private AudioFilter highPassFilter;
protected bool BackgroundBrightnessReduction protected bool BackgroundBrightnessReduction
{ {
@ -168,7 +170,8 @@ namespace osu.Game.Screens.Play
}, },
idleTracker = new IdleTracker(750), idleTracker = new IdleTracker(750),
}), }),
lowPassFilter = new AudioFilter(audio.TrackMixer) lowPassFilter = new AudioFilter(audio.TrackMixer),
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass)
}; };
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
@ -239,6 +242,7 @@ namespace osu.Game.Screens.Play
Beatmap.Value.Track.Stop(); Beatmap.Value.Track.Stop();
Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment);
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
highPassFilter.CutoffTo(0);
} }
public override bool OnExiting(IScreen next) public override bool OnExiting(IScreen next)
@ -266,9 +270,9 @@ namespace osu.Game.Screens.Play
const double duration = 300; const double duration = 300;
if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In); if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint);
logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint);
logo.FadeIn(350); logo.FadeIn(350);
Scheduler.AddDelayed(() => Scheduler.AddDelayed(() =>
@ -352,6 +356,7 @@ namespace osu.Game.Screens.Play
content.FadeInFromZero(400); content.FadeInFromZero(400);
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint);
highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in)
ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint));
} }
@ -364,6 +369,7 @@ namespace osu.Game.Screens.Play
content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint); content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint);
content.FadeOut(content_out_duration, Easing.OutQuint); content.FadeOut(content_out_duration, Easing.OutQuint);
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration);
highPassFilter.CutoffTo(0, content_out_duration);
} }
private void pushWhenLoaded() private void pushWhenLoaded()

View File

@ -270,7 +270,7 @@ namespace osu.Game.Screens.Play
colourNormal = colours.Yellow; colourNormal = colours.Yellow;
colourHover = colours.YellowDark; colourHover = colours.YellowDark;
sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); sampleConfirm = audio.Samples.Get(@"UI/submit-select");
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play
protected override APIRequest<APIScoreToken> CreateTokenRequest() protected override APIRequest<APIScoreToken> CreateTokenRequest()
{ {
if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) if (!(Beatmap.Value.BeatmapInfo.OnlineID is int beatmapId))
return null; return null;
if (!(Ruleset.Value.ID is int rulesetId) || Ruleset.Value.ID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) if (!(Ruleset.Value.ID is int rulesetId) || Ruleset.Value.ID > ILegacyRuleset.MAX_LEGACY_RULESET_ID)
@ -40,9 +40,9 @@ namespace osu.Game.Screens.Play
{ {
var beatmap = score.ScoreInfo.BeatmapInfo; var beatmap = score.ScoreInfo.BeatmapInfo;
Debug.Assert(beatmap.OnlineBeatmapID != null); Debug.Assert(beatmap.OnlineID != null);
int beatmapId = beatmap.OnlineBeatmapID.Value; int beatmapId = beatmap.OnlineID.Value;
return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo);
} }

View File

@ -241,7 +241,7 @@ namespace osu.Game.Screens.Play
if (!automaticDownload.Current.Value) if (!automaticDownload.Current.Value)
return; return;
if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID })) if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID }))
return; return;
beatmaps.Download(beatmapSet); beatmaps.Download(beatmapSet);

View File

@ -27,7 +27,7 @@ namespace osu.Game.Screens.Ranking
protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback) protected override APIRequest FetchScores(Action<IEnumerable<ScoreInfo>> scoresCallback)
{ {
if (Score.BeatmapInfo.OnlineBeatmapID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending) if (Score.BeatmapInfo.OnlineID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending)
return null; return null;
getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset);

View File

@ -1,9 +1,7 @@
// 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Screens.Ranking.Statistics namespace osu.Game.Screens.Ranking.Statistics
@ -11,7 +9,7 @@ namespace osu.Game.Screens.Ranking.Statistics
/// <summary> /// <summary>
/// Displays the unstable rate statistic for a given play. /// Displays the unstable rate statistic for a given play.
/// </summary> /// </summary>
public class UnstableRate : SimpleStatisticItem<double> public class UnstableRate : SimpleStatisticItem<double?>
{ {
/// <summary> /// <summary>
/// Creates and computes an <see cref="UnstableRate"/> statistic. /// Creates and computes an <see cref="UnstableRate"/> statistic.
@ -20,21 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics
public UnstableRate(IEnumerable<HitEvent> hitEvents) public UnstableRate(IEnumerable<HitEvent> hitEvents)
: base("Unstable Rate") : base("Unstable Rate")
{ {
double[] timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) Value = hitEvents.CalculateUnstableRate();
.Select(ev => ev.TimeOffset).ToArray();
Value = 10 * standardDeviation(timeOffsets);
} }
private static double standardDeviation(double[] timeOffsets) protected override string DisplayValue(double? value) => value == null ? "(not available)" : value.Value.ToString(@"N2");
{
if (timeOffsets.Length == 0)
return double.NaN;
double mean = timeOffsets.Average();
double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum();
return Math.Sqrt(squares / timeOffsets.Length);
}
protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2");
} }
} }

View File

@ -192,7 +192,7 @@ namespace osu.Game.Screens.Select
return; return;
} }
// for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). // for now, let's early abort if an OnlineID is not present (should have been populated at import time).
if (BeatmapInfo == null || BeatmapInfo.OnlineID <= 0 || api.State.Value == APIState.Offline) if (BeatmapInfo == null || BeatmapInfo.OnlineID <= 0 || api.State.Value == APIState.Offline)
{ {
updateMetrics(); updateMetrics();

View File

@ -66,8 +66,8 @@ namespace osu.Game.Screens.Select.Carousel
// this should be done after text matching so we can prioritise matching numbers in metadata. // this should be done after text matching so we can prioritise matching numbers in metadata.
if (!match && criteria.SearchNumber.HasValue) if (!match && criteria.SearchNumber.HasValue)
{ {
match = (BeatmapInfo.OnlineBeatmapID == criteria.SearchNumber.Value) || match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) ||
(BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); (BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value);
} }
} }

View File

@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}; };
sampleHover = audio.Samples.Get("SongSelect/song-ping"); sampleHover = audio.Samples.Get("UI/default-hover");
} }
public bool InsetForBorder public bool InsetForBorder

View File

@ -238,8 +238,8 @@ namespace osu.Game.Screens.Select.Carousel
if (editRequested != null) if (editRequested != null)
items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmapInfo))); items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmapInfo)));
if (beatmapInfo.OnlineBeatmapID.HasValue && beatmapOverlay != null) if (beatmapInfo.OnlineID.HasValue && beatmapOverlay != null)
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineBeatmapID.Value))); items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID.Value)));
if (collectionManager != null) if (collectionManager != null)
{ {

View File

@ -214,8 +214,8 @@ namespace osu.Game.Screens.Select.Carousel
if (Item.State.Value == CarouselItemState.NotSelected) if (Item.State.Value == CarouselItemState.NotSelected)
items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected));
if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) if (beatmapSet.OnlineID != null && viewDetails != null)
items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID.Value)));
if (collectionManager != null) if (collectionManager != null)
{ {

View File

@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Leaderboards
return null; return null;
} }
if (BeatmapInfo.OnlineBeatmapID == null || BeatmapInfo?.Status <= BeatmapSetOnlineStatus.Pending) if (BeatmapInfo.OnlineID == null || BeatmapInfo?.Status <= BeatmapSetOnlineStatus.Pending)
{ {
PlaceholderState = PlaceholderState.Unavailable; PlaceholderState = PlaceholderState.Unavailable;
return null; return null;

View File

@ -105,6 +105,8 @@ namespace osu.Game.Screens.Select
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>(); private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
private double audioFeedbackLastPlaybackTime;
[Resolved] [Resolved]
private MusicController music { get; set; } private MusicController music { get; set; }
@ -435,6 +437,7 @@ namespace osu.Game.Screens.Select
} }
// We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds.
private BeatmapInfo beatmapInfoPrevious;
private BeatmapInfo beatmapInfoNoDebounce; private BeatmapInfo beatmapInfoNoDebounce;
private RulesetInfo rulesetNoDebounce; private RulesetInfo rulesetNoDebounce;
@ -477,6 +480,21 @@ namespace osu.Game.Screens.Select
else else
selectionChangedDebounce = Scheduler.AddDelayed(run, 200); selectionChangedDebounce = Scheduler.AddDelayed(run, 200);
if (beatmap != beatmapInfoPrevious)
{
if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50)
{
if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID)
sampleChangeDifficulty.Play();
else
sampleChangeBeatmap.Play();
audioFeedbackLastPlaybackTime = Time.Current;
}
beatmapInfoPrevious = beatmap;
}
void run() void run()
{ {
// clear pending task immediately to track any potential nested debounce operation. // clear pending task immediately to track any potential nested debounce operation.
@ -508,18 +526,7 @@ namespace osu.Game.Screens.Select
if (!EqualityComparer<BeatmapInfo>.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) if (!EqualityComparer<BeatmapInfo>.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo))
{ {
Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\"");
int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID;
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap);
if (beatmap != null)
{
if (beatmap.BeatmapSetInfoID == lastSetID)
sampleChangeDifficulty.Play();
else
sampleChangeBeatmap.Play();
}
} }
if (this.IsCurrentScreen()) if (this.IsCurrentScreen())

View File

@ -84,7 +84,7 @@ namespace osu.Game.Screens.Spectate
if (!playingUserStates.TryGetValue(userId, out var userState)) if (!playingUserStates.TryGetValue(userId, out var userState))
continue; continue;
if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID))
updateGameplayState(userId); updateGameplayState(userId);
} }
} }
@ -150,7 +150,7 @@ namespace osu.Game.Screens.Spectate
if (resolvedRuleset == null) if (resolvedRuleset == null)
return; return;
var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID); var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineID == spectatorState.BeatmapID);
if (resolvedBeatmap == null) if (resolvedBeatmap == null)
return; return;

View File

@ -63,7 +63,7 @@ namespace osu.Game.Stores
validateOnlineIds(beatmapSet, realm); validateOnlineIds(beatmapSet, realm);
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0);
if (onlineLookupQueue != null) if (onlineLookupQueue != null)
{ {
@ -72,7 +72,7 @@ namespace osu.Game.Stores
} }
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0))
{ {
if (beatmapSet.OnlineID > 0) if (beatmapSet.OnlineID > 0)
{ {
@ -182,7 +182,7 @@ namespace osu.Game.Stores
return new RealmBeatmapSet return new RealmBeatmapSet
{ {
OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID ?? -1, OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID ?? -1,
// Metadata = beatmap.Metadata, // Metadata = beatmap.Metadata,
DateAdded = DateTimeOffset.UtcNow DateAdded = DateTimeOffset.UtcNow
}; };
@ -254,7 +254,7 @@ namespace osu.Game.Stores
{ {
Hash = hash, Hash = hash,
DifficultyName = decodedInfo.DifficultyName, DifficultyName = decodedInfo.DifficultyName,
OnlineID = decodedInfo.OnlineBeatmapID ?? -1, OnlineID = decodedInfo.OnlineID ?? -1,
AudioLeadIn = decodedInfo.AudioLeadIn, AudioLeadIn = decodedInfo.AudioLeadIn,
StackLeniency = decodedInfo.StackLeniency, StackLeniency = decodedInfo.StackLeniency,
SpecialStyle = decodedInfo.SpecialStyle, SpecialStyle = decodedInfo.SpecialStyle,

View File

@ -35,10 +35,10 @@ namespace osu.Game.Tests.Beatmaps
BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.RulesetID = ruleset.ID ?? 0;
BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata;
BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo }; BeatmapInfo.BeatmapSet.Beatmaps = new List<BeatmapInfo> { BeatmapInfo };
BeatmapInfo.BeatmapSet.OnlineBeatmapSetID = Interlocked.Increment(ref onlineSetID); BeatmapInfo.BeatmapSet.OnlineID = Interlocked.Increment(ref onlineSetID);
BeatmapInfo.Length = 75000; BeatmapInfo.Length = 75000;
BeatmapInfo.OnlineInfo = new APIBeatmap(); BeatmapInfo.OnlineInfo = new APIBeatmap();
BeatmapInfo.OnlineBeatmapID = Interlocked.Increment(ref onlineBeatmapID); BeatmapInfo.OnlineID = Interlocked.Increment(ref onlineBeatmapID);
} }
protected virtual Beatmap CreateBeatmap() => createTestBeatmap(); protected virtual Beatmap CreateBeatmap() => createTestBeatmap();

View File

@ -311,7 +311,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID); var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID);
IBeatmapSetInfo? set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet IBeatmapSetInfo? set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet
?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet; ?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId)?.BeatmapSet;
if (set == null) if (set == null)
throw new InvalidOperationException("Beatmap not found."); throw new InvalidOperationException("Beatmap not found.");

View File

@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual
return new APIBeatmapSet return new APIBeatmapSet
{ {
OnlineID = beatmap.BeatmapSet.OnlineID, OnlineID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID,
Status = BeatmapSetOnlineStatus.Ranked, Status = BeatmapSetOnlineStatus.Ranked,
Covers = new BeatmapSetOnlineCovers Covers = new BeatmapSetOnlineCovers
{ {
@ -222,8 +222,8 @@ namespace osu.Game.Tests.Visual
{ {
new APIBeatmap new APIBeatmap
{ {
OnlineID = beatmap.OnlineID, OnlineID = ((IBeatmapInfo)beatmap).OnlineID,
OnlineBeatmapSetID = beatmap.BeatmapSet.OnlineID, OnlineBeatmapSetID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID,
Status = beatmap.Status, Status = beatmap.Status,
Checksum = beatmap.MD5Hash, Checksum = beatmap.MD5Hash,
AuthorID = beatmap.Metadata.Author.OnlineID, AuthorID = beatmap.Metadata.Author.OnlineID,

View File

@ -9,6 +9,34 @@ namespace osu.Game.Utils
{ {
public static class ZipUtils public static class ZipUtils
{ {
public static bool IsZipArchive(MemoryStream stream)
{
try
{
stream.Seek(0, SeekOrigin.Begin);
using (var arc = ZipArchive.Open(stream))
{
foreach (var entry in arc.Entries)
{
using (entry.OpenEntryStream())
{
}
}
}
return true;
}
catch (Exception)
{
return false;
}
finally
{
stream.Seek(0, SeekOrigin.Begin);
}
}
public static bool IsZipArchive(string path) public static bool IsZipArchive(string path)
{ {
if (!File.Exists(path)) if (!File.Exists(path))

View File

@ -37,7 +37,7 @@
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.6.0" /> <PackageReference Include="Realm" Version="10.6.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1108.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.1108.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
<PackageReference Include="Sentry" Version="3.10.0" /> <PackageReference Include="Sentry" Version="3.10.0" />
<PackageReference Include="SharpCompress" Version="0.30.0" /> <PackageReference Include="SharpCompress" Version="0.30.0" />
<PackageReference Include="NUnit" Version="3.13.2" /> <PackageReference Include="NUnit" Version="3.13.2" />

View File

@ -71,7 +71,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1108.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1108.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
<PropertyGroup> <PropertyGroup>