1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-26 21:42:56 +08:00

Merge branch 'master' into multi-queueing-modes

This commit is contained in:
smoogipoo 2021-11-05 15:57:25 +09:00
commit e6deb0c873
169 changed files with 1915 additions and 1324 deletions

View File

@ -14,8 +14,8 @@
"jb"
]
},
"smoogipoo.nvika": {
"version": "1.0.3",
"nvika": {
"version": "2.2.0",
"commands": [
"nvika"
]

View File

@ -16,7 +16,7 @@
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="All" />
</ItemGroup>

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -10,7 +10,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1026.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1029.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1104.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -20,6 +20,7 @@ namespace osu.Android
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")]
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")]

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
Replay = new CatchAutoGenerator(beatmap).Generate(),
};
}

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } },
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } },
Replay = new CatchAutoGenerator(beatmap).Generate(),
};
}

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } },
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
};
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList<Mod> mods) => new Score
{
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } },
ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } },
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
};
}

View File

@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
[TestCase(6.5867229481955389d, "diffcalc-test")]
[TestCase(1.0416315570967911d, "zero-length-sliders")]
[TestCase(6.5295339534769958d, "diffcalc-test")]
[TestCase(1.1514260533755143d, "zero-length-sliders")]
public void Test(double expected, string name)
=> base.Test(expected, name);
[TestCase(8.2730989071947896d, "diffcalc-test")]
[TestCase(1.2726413186221039d, "zero-length-sliders")]
[TestCase(9.047752485219954d, "diffcalc-test")]
[TestCase(1.3985711787077566d, "zero-length-sliders")]
public void TestClockRateAdjusted(double expected, string name)
=> Test(expected, name, new OsuModDoubleTime());

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 NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestSceneNoSpinnerStacking : TestSceneOsuPlayer
{
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
{
BeatmapInfo = new BeatmapInfo
{
BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 },
Ruleset = ruleset
}
};
for (int i = 0; i < 512; i++)
{
if (i % 32 < 20)
beatmap.HitObjects.Add(new Spinner { Position = new Vector2(256, 192), StartTime = i * 200, EndTime = (i * 200) + 100 });
}
return beatmap;
}
}
}

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />

View File

@ -12,20 +12,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
public class OsuDifficultyHitObject : DifficultyHitObject
{
private const int normalized_radius = 52;
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;
protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject;
/// <summary>
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms to account for simultaneous <see cref="OsuDifficultyHitObject"/>s.
/// </summary>
public double StrainTime { get; private set; }
/// <summary>
/// Normalized distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
/// </summary>
public double JumpDistance { get; private set; }
/// <summary>
/// Minimum distance from the end position of the previous <see cref="OsuDifficultyHitObject"/> to the start position of this <see cref="OsuDifficultyHitObject"/>.
/// </summary>
public double MovementDistance { get; private set; }
/// <summary>
/// Normalized distance between the start and end position of the previous <see cref="OsuDifficultyHitObject"/>.
/// </summary>
@ -37,6 +38,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
/// </summary>
public double? Angle { get; private set; }
/// <summary>
/// Milliseconds elapsed since the end time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
/// </summary>
public double MovementTime { get; private set; }
/// <summary>
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/> to the end time of the same previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
/// </summary>
public double TravelTime { get; private set; }
/// <summary>
/// Milliseconds elapsed since the start time of the previous <see cref="OsuDifficultyHitObject"/>, with a minimum of 25ms.
/// </summary>
public readonly double StrainTime;
private readonly OsuHitObject lastLastObject;
private readonly OsuHitObject lastObject;
@ -46,13 +62,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
this.lastLastObject = (OsuHitObject)lastLastObject;
this.lastObject = (OsuHitObject)lastObject;
setDistances();
// Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects.
StrainTime = Math.Max(DeltaTime, min_delta_time);
// Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects.
StrainTime = Math.Max(DeltaTime, 25);
setDistances(clockRate);
}
private void setDistances()
private void setDistances(double clockRate)
{
// We don't need to calculate either angle or distance when one of the last->curr objects is a spinner
if (BaseObject is Spinner || lastObject is Spinner)
@ -67,15 +83,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus;
}
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
if (lastObject is Slider lastSlider)
{
computeSliderCursorPosition(lastSlider);
TravelDistance = lastSlider.LazyTravelDistance * scalingFactor;
TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time);
MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time);
// Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance.
float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor;
// For hitobjects which continue in the direction of the slider, the player will normally follow through the slider,
// such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider.
// In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance.
MovementDistance = Math.Min(JumpDistance, tailJumpDistance);
}
else
{
MovementTime = StrainTime;
MovementDistance = JumpDistance;
}
Vector2 lastCursorPosition = getEndCursorPosition(lastObject);
JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length;
if (lastLastObject != null && !(lastLastObject is Spinner))
{
@ -98,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
slider.LazyEndPosition = slider.StackedPosition;
float approxFollowCircleRadius = (float)(slider.Radius * 3);
float followCircleRadius = (float)(slider.Radius * 2.4);
var computeVertex = new Action<double>(t =>
{
double progress = (t - slider.StartTime) / slider.SpanDuration;
@ -111,11 +141,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value;
float dist = diff.Length;
if (dist > approxFollowCircleRadius)
slider.LazyTravelTime = t - slider.StartTime;
if (dist > followCircleRadius)
{
// The cursor would be outside the follow circle, we need to move it
diff.Normalize(); // Obtain direction of diff
dist -= approxFollowCircleRadius;
dist -= followCircleRadius;
slider.LazyEndPosition += diff * dist;
slider.LazyTravelDistance += dist;
}

View File

@ -14,53 +14,96 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
/// </summary>
public class Aim : OsuStrainSkill
{
private const double angle_bonus_begin = Math.PI / 3;
private const double timing_threshold = 107;
public Aim(Mod[] mods)
: base(mods)
{
}
protected override int HistoryLength => 2;
private const double wide_angle_multiplier = 1.5;
private const double acute_angle_multiplier = 2.0;
private double currentStrain = 1;
private double skillMultiplier => 26.25;
private double skillMultiplier => 23.25;
private double strainDecayBase => 0.15;
private double strainValueOf(DifficultyHitObject current)
{
if (current.BaseObject is Spinner)
if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner)
return 0;
var osuCurrent = (OsuDifficultyHitObject)current;
var osuCurrObj = (OsuDifficultyHitObject)current;
var osuLastObj = (OsuDifficultyHitObject)Previous[0];
var osuLastLastObj = (OsuDifficultyHitObject)Previous[1];
double aimStrain = 0;
// Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle.
double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime;
if (Previous.Count > 0)
// 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)
{
var osuPrevious = (OsuDifficultyHitObject)Previous[0];
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.
if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin)
currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity.
}
// As above, do the same for the previous hitobject.
double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime;
if (osuLastLastObj.BaseObject is Slider)
{
double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime;
double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime;
prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity);
}
double angleBonus = 0;
double aimStrain = currVelocity; // Start strain with regular velocity.
if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same.
{
if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null)
{
const double scale = 90;
double currAngle = osuCurrObj.Angle.Value;
double lastAngle = osuLastObj.Angle.Value;
double lastLastAngle = osuLastLastObj.Angle.Value;
double angleBonus = Math.Sqrt(
Math.Max(osuPrevious.JumpDistance - scale, 0)
* Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2)
* Math.Max(osuCurrent.JumpDistance - scale, 0));
aimStrain = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime);
// Rewarding angles, take the smaller velocity as base.
angleBonus = Math.Min(currVelocity, prevVelocity);
double wideAngleBonus = calcWideAngleBonus(currAngle);
double acuteAngleBonus = calcAcuteAngleBonus(currAngle);
if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2.
acuteAngleBonus = 0;
else
{
acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern.
* Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime
* Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4
* Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter).
}
wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute.
acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse.
angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the angle buffs together.
}
}
double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance);
double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance);
aimStrain += angleBonus; // Add in angle bonus.
return Math.Max(
aimStrain + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold),
(Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime
);
return aimStrain;
}
private double calcWideAngleBonus(double angle) => Math.Pow(Math.Sin(3.0 / 4 * (Math.Min(5.0 / 6 * Math.PI, Math.Max(Math.PI / 6, angle)) - Math.PI / 6)), 2);
private double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle);
private double applyDiminishingExp(double val) => Math.Pow(val, 0.99);
private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000);

View File

@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects
set => StackHeightBindable.Value = value;
}
public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f);
public double Radius => OBJECT_RADIUS * Scale;

View File

@ -79,6 +79,12 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
internal float LazyTravelDistance;
/// <summary>
/// The time taken by the cursor upon completion of this <see cref="Slider"/> if it was hit
/// with as few movements as possible. This is set and used by difficulty calculation.
/// </summary>
internal double LazyTravelTime;
public IList<IList<HitSampleInfo>> NodeSamples { get; set; } = new List<IList<HitSampleInfo>>();
[JsonIgnore]

View File

@ -8,6 +8,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
public int MaximumBonusSpins { get; protected set; } = 1;
public override Vector2 StackOffset => Vector2.Zero;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);

View File

@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />

View File

@ -673,6 +673,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null));
// ReSharper disable once HeuristicUnreachableCode
// weird one, see https://youtrack.jetbrains.com/issue/RIDER-70159.
Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier));
Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15)));

View File

@ -846,6 +846,42 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
// TODO: needs to be pulled across to realm implementation when this file is nuked.
[Test]
public void TestSaveRemovesInvalidCharactersFromPath()
{
// unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest)))
{
try
{
var osu = LoadOsuIntoHost(host);
var manager = osu.Dependencies.Get<BeatmapManager>();
var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER);
var beatmap = working.Beatmap;
beatmap.BeatmapInfo.Version = "difficulty";
beatmap.BeatmapInfo.Metadata = new BeatmapMetadata
{
Artist = "Artist/With\\Slashes",
Title = "Title",
AuthorString = "mapper",
};
manager.Save(beatmap.BeatmapInfo, working.Beatmap);
Assert.AreEqual("Artist_With_Slashes - Title (mapper) [difficulty].osu", beatmap.BeatmapInfo.Path);
}
finally
{
host.Exit();
}
}
}
[Test]
public void TestCreateNewEmptyBeatmap()
{

View File

@ -35,7 +35,7 @@ namespace osu.Game.Tests.Editing.Checks
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.AudioFile = null;
beatmap.Metadata.AudioFile = string.Empty;
var mock = new Mock<IWorkingBeatmap>();
mock.SetupGet(w => w.Beatmap).Returns(beatmap);

View File

@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks
public void TestMissing()
{
// While this is a problem, it is out of scope for this check and is caught by a different one.
beatmap.Metadata.BackgroundFile = null;
beatmap.Metadata.BackgroundFile = string.Empty;
var context = getContext(null, System.Array.Empty<byte>());
Assert.That(check.Run(context), Is.Empty);

View File

@ -65,7 +65,7 @@ namespace osu.Game.Tests.Editing.Checks
[Test]
public void TestBackgroundNotSet()
{
beatmap.Metadata.BackgroundFile = null;
beatmap.Metadata.BackgroundFile = string.Empty;
var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
var issues = check.Run(context).ToList();

View File

@ -39,8 +39,8 @@ namespace osu.Game.Tests.NonVisual
[Test]
public void TestCheckNullID()
{
var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved };
var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved };
var ourInfo = new BeatmapSetInfo { Hash = "1" };
var otherInfo = new BeatmapSetInfo { Hash = "2" };
Assert.AreNotEqual(ourInfo, otherInfo);
}

View File

@ -189,7 +189,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered)
{
var exampleBeatmapInfo = getExampleBeatmap();
exampleBeatmapInfo.Metadata.ArtistUnicode = null;
exampleBeatmapInfo.Metadata.ArtistUnicode = string.Empty;
var criteria = new FilterCriteria
{

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NUnit.Framework;
@ -67,9 +68,11 @@ namespace osu.Game.Tests.Online
var deserialised = JsonConvert.DeserializeObject<APIMod>(JsonConvert.SerializeObject(apiMod));
var converted = (TestModTimeRamp)deserialised?.ToMod(new TestRuleset());
Assert.That(converted?.AdjustPitch.Value, Is.EqualTo(false));
Assert.That(converted?.InitialRate.Value, Is.EqualTo(1.25));
Assert.That(converted?.FinalRate.Value, Is.EqualTo(0.25));
Assert.That(converted, Is.Not.Null);
Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false));
Assert.That(converted.InitialRate.Value, Is.EqualTo(1.25));
Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25));
}
[Test]
@ -121,11 +124,11 @@ namespace osu.Game.Tests.Online
new TestModDifficultyAdjust()
};
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new System.NotImplementedException();
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = string.Empty;
public override string ShortName { get; } = string.Empty;

View File

@ -38,6 +38,15 @@ namespace osu.Game.Tests.Skins.IO
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
});
[Test]
public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu =>
{
var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk"));
// When the import filename doesn't match, it should be appended (and update the skin.ini).
assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu);
});
[Test]
public Task TestSingleImportMatchingFilename() => runSkinTest(async osu =>
{
@ -199,21 +208,23 @@ namespace osu.Game.Tests.Skins.IO
return zipStream;
}
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini")
private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini", bool includeSectionHeader = true)
{
var zipStream = new MemoryStream();
using var zip = ZipArchive.Create();
zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique));
zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique, includeSectionHeader));
zip.SaveTo(zipStream);
return zipStream;
}
private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true)
private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true, bool includeSectionHeader = true)
{
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.WriteLine("[General]");
if (includeSectionHeader)
writer.WriteLine("[General]");
writer.WriteLine($"Name: {name}");
writer.WriteLine($"Author: {author}");

View File

@ -0,0 +1,181 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Beatmaps
{
public class TestSceneBeatmapCard : OsuTestScene
{
private APIBeatmapSet[] testCases;
#region Test case generation
[BackgroundDependencyLoader]
private void load()
{
var normal = CreateAPIBeatmapSet(Ruleset.Value);
normal.HasVideo = true;
normal.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var someDifficulties = getManyDifficultiesBeatmapSet(11);
someDifficulties.Title = someDifficulties.TitleUnicode = "some difficulties";
someDifficulties.Status = BeatmapSetOnlineStatus.Qualified;
var manyDifficulties = getManyDifficultiesBeatmapSet(100);
manyDifficulties.Status = BeatmapSetOnlineStatus.Pending;
var explicitMap = CreateAPIBeatmapSet(Ruleset.Value);
explicitMap.HasExplicitContent = true;
var featuredMap = CreateAPIBeatmapSet(Ruleset.Value);
featuredMap.TrackId = 1;
var explicitFeaturedMap = CreateAPIBeatmapSet(Ruleset.Value);
explicitFeaturedMap.HasExplicitContent = true;
explicitFeaturedMap.TrackId = 2;
var longName = CreateAPIBeatmapSet(Ruleset.Value);
longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title";
longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name.";
longName.HasExplicitContent = true;
longName.TrackId = 444;
testCases = new[]
{
normal,
undownloadable,
someDifficulties,
manyDifficulties,
explicitMap,
featuredMap,
explicitFeaturedMap,
longName
};
}
private APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet
{
OnlineID = 123,
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new User
{
Username = "BanchoBot",
Id = 3,
},
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new[]
{
new APIBeatmap
{
RulesetID = Ruleset.Value.OnlineID,
DifficultyName = "Test",
StarRating = 6.42,
}
}
};
private static APIBeatmapSet getManyDifficultiesBeatmapSet(int count)
{
var beatmaps = new List<APIBeatmap>();
for (int i = 0; i < count; i++)
{
beatmaps.Add(new APIBeatmap
{
RulesetID = i % 4,
StarRating = 2 + i % 4 * 2,
});
}
return new APIBeatmapSet
{
OnlineID = 1,
Title = "many difficulties beatmap",
Artist = "test",
Author = new User
{
Username = "BanchoBot",
Id = 3,
},
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = beatmaps.ToArray(),
};
}
#endregion
private Drawable createContent(OverlayColourScheme colourScheme, Func<APIBeatmapSet, Drawable> creationFunc)
{
var colourProvider = new OverlayColourProvider(colourScheme);
return new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(OverlayColourProvider), colourProvider)
},
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5
},
new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
Padding = new MarginPadding(10),
Spacing = new Vector2(10),
ChildrenEnumerable = testCases.Select(creationFunc)
}
}
}
};
}
private void createTestCase(Func<APIBeatmapSet, Drawable> creationFunc)
{
foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast<OverlayColourScheme>())
AddStep($"set {scheme} scheme", () => Child = createContent(scheme, creationFunc));
}
[Test]
public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo));
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Beatmaps
{
RulesetID = difficulty.rulesetId,
StarRating = difficulty.stars
}).ToList()
}).ToArray()
};
[Test]

View File

@ -5,7 +5,10 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
@ -29,6 +32,9 @@ namespace osu.Game.Tests.Visual.Editing
private ComposeBlueprintContainer blueprintContainer
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First();
private ContextMenuContainer contextMenuContainer
=> Editor.ChildrenOfType<ContextMenuContainer>().First();
private void moveMouseToObject(Func<HitObject> targetFunc)
{
AddStep("move mouse to object", () =>
@ -42,6 +48,19 @@ namespace osu.Game.Tests.Visual.Editing
});
}
[Test]
public void TestSelectAndShowContextMenu()
{
var addedObject = new HitCircle { StartTime = 100, Position = new Vector2(100, 100) };
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
moveMouseToObject(() => addedObject);
AddStep("right click", () => InputManager.Click(MouseButton.Right));
AddUntilStep("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject);
AddUntilStep("context menu is visible", () => contextMenuContainer.ChildrenOfType<OsuContextMenu>().Single().State == MenuState.Open);
}
[Test]
public void TestNudgeSelection()
{

View File

@ -23,10 +23,10 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set metadata", () =>
{
editorBeatmap.Metadata.Artist = "Example Artist";
editorBeatmap.Metadata.ArtistUnicode = null;
editorBeatmap.Metadata.ArtistUnicode = string.Empty;
editorBeatmap.Metadata.Title = "Example Title";
editorBeatmap.Metadata.TitleUnicode = null;
editorBeatmap.Metadata.TitleUnicode = string.Empty;
});
createSection();
@ -44,10 +44,10 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set metadata", () =>
{
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
editorBeatmap.Metadata.Artist = null;
editorBeatmap.Metadata.Artist = string.Empty;
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
editorBeatmap.Metadata.Title = null;
editorBeatmap.Metadata.Title = string.Empty;
});
createSection();
@ -86,10 +86,10 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set metadata", () =>
{
editorBeatmap.Metadata.ArtistUnicode = "*なみりん";
editorBeatmap.Metadata.Artist = null;
editorBeatmap.Metadata.Artist = string.Empty;
editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット";
editorBeatmap.Metadata.Title = null;
editorBeatmap.Metadata.Title = string.Empty;
});
createSection();

View File

@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay
return new APIScoreInfo
{
OnlineID = 2553163309,
OnlineRulesetID = 0,
RulesetID = 0,
Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(),
HasReplay = replayAvailable,
User = new User

View File

@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Gameplay
ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo }
})
{
ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos)
ScreenSpaceToGamefield = pos => recordingManager?.ToLocalSpace(pos) ?? Vector2.Zero,
},
Child = new Container
{
@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
ReplayInputHandler = new TestFramedReplayInputHandler(replay)
{
GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos),
GamefieldToScreenSpace = pos => playbackManager?.ToScreenSpace(pos) ?? Vector2.Zero,
},
Child = new Container
{

View File

@ -67,6 +67,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
}),
createLoungeRoom(new Room
{
Name = { Value = "Multiplayer room" },
Status = { Value = new RoomStatusOpen() },
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Type = { Value = MatchType.HeadToHead },
Playlist =
{
new PlaylistItem
{
Beatmap =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarDifficulty = 2.5,
Metadata =
{
Artist = "very very very very very very very very very long artist",
ArtistUnicode = "very very very very very very very very very long artist",
Title = "very very very very very very very very very very very long title",
TitleUnicode = "very very very very very very very very very very very long title",
}
}
}.BeatmapInfo,
}
}
}
}),
createLoungeRoom(new Room
{
Name = { Value = "Playlist room with multiple beatmaps" },
Status = { Value = new RoomStatusPlaying() },

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -223,11 +224,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestDownloadButtonVisibleInitiallyWhenBeatmapDoesNotExist()
{
var byOnlineId = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
byOnlineId.BeatmapSet.OnlineBeatmapSetID = 1337; // Some random ID that does not exist locally.
var byOnlineId = CreateAPIBeatmap();
byOnlineId.OnlineID = 1337; // Some random ID that does not exist locally.
var byChecksum = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
byChecksum.MD5Hash = "1337"; // Some random checksum that does not exist locally.
var byChecksum = CreateAPIBeatmap();
byChecksum.Checksum = "1337"; // Some random checksum that does not exist locally.
createPlaylist(byOnlineId, byChecksum);
@ -237,8 +238,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestExplicitBeatmapItem()
{
var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo;
beatmap.BeatmapSet.OnlineInfo.HasExplicitContent = true;
var beatmap = CreateAPIBeatmap();
Debug.Assert(beatmap.BeatmapSet != null);
beatmap.BeatmapSet.HasExplicitContent = true;
createPlaylist(beatmap);
}
@ -355,7 +359,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded));
}
private void createPlaylist(params BeatmapInfo[] beatmaps)
private void createPlaylist(params IBeatmapInfo[] beatmaps)
{
AddStep("create playlist", () =>
{

View File

@ -231,6 +231,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
}
});
AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1);
AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
}
[Test]
@ -290,6 +293,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
AddUntilStep("wait for join", () => client.Room != null);
AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1);
AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1);
}
[Test]

View File

@ -45,11 +45,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
public void TestAddNullUser()
public void TestAddUnresolvedUser()
{
AddAssert("one unique panel", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 1);
AddStep("add non-resolvable user", () => Client.AddNullUser());
AddStep("add non-resolvable user", () => Client.TestAddUnresolvedUser());
AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1);
AddUntilStep("two unique panels", () => this.ChildrenOfType<ParticipantPanel>().Select(p => p.User).Distinct().Count() == 2);

View File

@ -21,15 +21,12 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestUndownloadableWithLink()
{
AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo
AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
Availability = new BeatmapSetOnlineAvailability
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
ExternalLink = @"https://osu.ppy.sh",
},
DownloadDisabled = true,
ExternalLink = @"https://osu.ppy.sh",
},
});
@ -39,14 +36,11 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestUndownloadableNoLink()
{
AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new BeatmapSetInfo
AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
Availability = new BeatmapSetOnlineAvailability
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
DownloadDisabled = true,
},
});
@ -56,15 +50,12 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestPartsRemovedWithLink()
{
AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo
AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
Availability = new BeatmapSetOnlineAvailability
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = false,
ExternalLink = @"https://osu.ppy.sh",
},
DownloadDisabled = false,
ExternalLink = @"https://osu.ppy.sh",
},
});
@ -74,14 +65,11 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestNormal()
{
AddStep("set normal beatmapset", () => container.BeatmapSet = new BeatmapSetInfo
AddStep("set normal beatmapset", () => container.BeatmapSet = new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
Availability = new BeatmapSetOnlineAvailability
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = false,
},
DownloadDisabled = false,
},
});

View File

@ -1,15 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Tests.Visual.Online
{
@ -35,9 +34,9 @@ namespace osu.Game.Tests.Visual.Online
AddStep("load multiple rulesets beatmapset", () =>
{
selector.BeatmapSet = new BeatmapSetInfo
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = enabledRulesets.Select(r => new BeatmapInfo { Ruleset = r }).ToList()
Beatmaps = enabledRulesets.Select(r => new APIBeatmap { RulesetID = r.OnlineID }).ToArray()
};
});
@ -53,13 +52,13 @@ namespace osu.Game.Tests.Visual.Online
AddStep("load single ruleset beatmapset", () =>
{
selector.BeatmapSet = new BeatmapSetInfo
selector.BeatmapSet = new APIBeatmapSet
{
Beatmaps = new List<BeatmapInfo>
Beatmaps = new[]
{
new BeatmapInfo
new APIBeatmap
{
Ruleset = enabledRuleset
RulesetID = enabledRuleset.OnlineID
}
}
};
@ -71,10 +70,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestEmptyBeatmapSet()
{
AddStep("load empty beatmapset", () => selector.BeatmapSet = new BeatmapSetInfo
{
Beatmaps = new List<BeatmapInfo>()
});
AddStep("load empty beatmapset", () => selector.BeatmapSet = new APIBeatmapSet());
AddAssert("no ruleset selected", () => selector.SelectedTab == null);
AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value));

View File

@ -49,60 +49,48 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep(@"show first", () =>
{
overlay.ShowBeatmapSet(new BeatmapSetInfo
overlay.ShowBeatmapSet(new APIBeatmapSet
{
OnlineBeatmapSetID = 1235,
Metadata = new BeatmapMetadata
OnlineID = 1235,
Title = @"an awesome beatmap",
Artist = @"naru narusegawa",
Source = @"hinata sou",
Tags = @"test tag tag more tag",
Author = new User
{
Title = @"an awesome beatmap",
Artist = @"naru narusegawa",
Source = @"hinata sou",
Tags = @"test tag tag more tag",
Author = new User
{
Username = @"BanchoBot",
Id = 3,
},
Username = @"BanchoBot",
Id = 3,
},
OnlineInfo = new APIBeatmapSet
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
Submitted = DateTime.Now,
Ranked = DateTime.Now,
BPM = 111,
HasVideo = true,
Ratings = Enumerable.Range(0, 11).ToArray(),
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new[]
{
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
Submitted = DateTime.Now,
Ranked = DateTime.Now,
BPM = 111,
HasVideo = true,
Ratings = Enumerable.Range(0, 11).ToArray(),
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
new APIBeatmap
{
StarDifficulty = 9.99,
Version = @"TEST",
StarRating = 9.99,
DifficultyName = @"TEST",
Length = 456000,
Ruleset = rulesets.GetRuleset(3),
BaseDifficulty = new BeatmapDifficulty
RulesetID = 3,
CircleSize = 1,
DrainRate = 2.3f,
OverallDifficulty = 4.5f,
ApproachRate = 6,
CircleCount = 111,
SliderCount = 12,
PlayCount = 222,
PassCount = 21,
FailTimes = new APIFailTimes
{
CircleSize = 1,
DrainRate = 2.3f,
OverallDifficulty = 4.5f,
ApproachRate = 6,
},
OnlineInfo = new APIBeatmap
{
CircleCount = 111,
SliderCount = 12,
PlayCount = 222,
PassCount = 21,
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
@ -120,71 +108,15 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep(@"show undownloadable", () =>
{
overlay.ShowBeatmapSet(new BeatmapSetInfo
var set = getBeatmapSet();
set.Availability = new BeatmapSetOnlineAvailability
{
OnlineBeatmapSetID = 1234,
Metadata = new BeatmapMetadata
{
Title = @"undownloadable beatmap",
Artist = @"no one",
Source = @"some source",
Tags = @"another test tag tag more test tags",
Author = new User
{
Username = @"BanchoBot",
Id = 3,
},
},
OnlineInfo = new APIBeatmapSet
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
ExternalLink = "https://osu.ppy.sh",
},
Preview = @"https://b.ppy.sh/preview/1234.mp3",
PlayCount = 123,
FavouriteCount = 456,
Submitted = DateTime.Now,
Ranked = DateTime.Now,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" },
Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" },
Ratings = Enumerable.Range(0, 11).ToArray(),
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
StarDifficulty = 5.67,
Version = @"ANOTHER TEST",
Length = 123000,
Ruleset = rulesets.GetRuleset(1),
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 9,
DrainRate = 8,
OverallDifficulty = 7,
ApproachRate = 6,
},
OnlineInfo = new APIBeatmap
{
CircleCount = 123,
SliderCount = 45,
PlayCount = 567,
PassCount = 89,
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
},
},
},
});
DownloadDisabled = true,
ExternalLink = "https://osu.ppy.sh",
};
overlay.ShowBeatmapSet(set);
});
downloadAssert(false);
@ -195,48 +127,30 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("show multiple rulesets beatmap", () =>
{
var beatmaps = new List<BeatmapInfo>();
var beatmaps = new List<APIBeatmap>();
foreach (var ruleset in rulesets.AvailableRulesets.Skip(1))
{
beatmaps.Add(new BeatmapInfo
beatmaps.Add(new APIBeatmap
{
Version = ruleset.Name,
Ruleset = ruleset,
BaseDifficulty = new BeatmapDifficulty(),
OnlineInfo = new APIBeatmap
DifficultyName = ruleset.Name,
RulesetID = ruleset.OnlineID,
FailTimes = new APIFailTimes
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
});
}
overlay.ShowBeatmapSet(new BeatmapSetInfo
{
Metadata = new BeatmapMetadata
{
Title = @"multiple rulesets beatmap",
Artist = @"none",
Author = new User
{
Username = "BanchoBot",
Id = 3,
}
},
OnlineInfo = new APIBeatmapSet
{
Covers = new BeatmapSetOnlineCovers(),
Ratings = Enumerable.Range(0, 11).ToArray(),
},
Beatmaps = beatmaps
});
var set = getBeatmapSet();
set.Beatmaps = beatmaps.ToArray();
overlay.ShowBeatmapSet(set);
});
AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.BeatmapInfo.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value)));
AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.OnlineID == overlay.Header.RulesetSelector.Current.Value.OnlineID));
AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected);
}
@ -246,7 +160,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("show explicit map", () =>
{
var beatmapSet = getBeatmapSet();
beatmapSet.OnlineInfo.HasExplicitContent = true;
beatmapSet.HasExplicitContent = true;
overlay.ShowBeatmapSet(beatmapSet);
});
}
@ -257,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("show featured map", () =>
{
var beatmapSet = getBeatmapSet();
beatmapSet.OnlineInfo.TrackId = 1;
beatmapSet.TrackId = 1;
overlay.ShowBeatmapSet(beatmapSet);
});
}
@ -274,63 +188,41 @@ namespace osu.Game.Tests.Visual.Online
AddStep(@"show without reload", overlay.Show);
}
private BeatmapSetInfo createManyDifficultiesBeatmapSet()
private APIBeatmapSet createManyDifficultiesBeatmapSet()
{
var beatmaps = new List<BeatmapInfo>();
var set = getBeatmapSet();
var beatmaps = new List<APIBeatmap>();
for (int i = 1; i < 41; i++)
{
beatmaps.Add(new BeatmapInfo
beatmaps.Add(new APIBeatmap
{
OnlineBeatmapID = i * 10,
Version = $"Test #{i}",
Ruleset = Ruleset.Value,
StarDifficulty = 2 + i * 0.1,
BaseDifficulty = new BeatmapDifficulty
OnlineID = i * 10,
DifficultyName = $"Test #{i}",
RulesetID = Ruleset.Value.ID ?? -1,
StarRating = 2 + i * 0.1,
OverallDifficulty = 3.5f,
FailTimes = new APIFailTimes
{
OverallDifficulty = 3.5f,
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
},
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(),
},
}
});
}
return new BeatmapSetInfo
{
OnlineBeatmapSetID = 123,
Metadata = new BeatmapMetadata
{
Title = @"many difficulties beatmap",
Artist = @"none",
Author = new User
{
Username = @"BanchoBot",
Id = 3,
},
},
OnlineInfo = new APIBeatmapSet
{
Preview = @"https://b.ppy.sh/preview/123.mp3",
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Ratings = Enumerable.Range(0, 11).ToArray(),
},
Beatmaps = beatmaps,
};
set.Beatmaps = beatmaps.ToArray();
return set;
}
private BeatmapSetInfo getBeatmapSet()
private APIBeatmapSet getBeatmapSet()
{
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value);
// Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`).
beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++;
beatmapSet.OnlineID = nextBeatmapSetId++;
return beatmapSet;
}

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -44,27 +43,21 @@ namespace osu.Game.Tests.Visual.Online
AddStep("set second set", () => details.BeatmapSet = secondSet);
AddAssert("ratings set", () => details.Ratings.Ratings == secondSet.Ratings);
static BeatmapSetInfo createSet() => new BeatmapSetInfo
static APIBeatmapSet createSet() => new APIBeatmapSet
{
Beatmaps = new List<BeatmapInfo>
Beatmaps = new[]
{
new BeatmapInfo
new APIBeatmap
{
OnlineInfo = new APIBeatmap
FailTimes = new APIFailTimes
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
},
}
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
},
}
},
OnlineInfo = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray(),
Status = BeatmapSetOnlineStatus.Ranked
}
Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray(),
Status = BeatmapSetOnlineStatus.Ranked
};
}

View File

@ -59,21 +59,18 @@ namespace osu.Game.Tests.Visual.Online
var firstBeatmap = createBeatmap();
var secondBeatmap = createBeatmap();
AddStep("set first set", () => successRate.BeatmapInfo = firstBeatmap);
AddStep("set first set", () => successRate.Beatmap = firstBeatmap);
AddAssert("ratings set", () => successRate.Graph.FailTimes == firstBeatmap.FailTimes);
AddStep("set second set", () => successRate.BeatmapInfo = secondBeatmap);
AddStep("set second set", () => successRate.Beatmap = secondBeatmap);
AddAssert("ratings set", () => successRate.Graph.FailTimes == secondBeatmap.FailTimes);
static BeatmapInfo createBeatmap() => new BeatmapInfo
static APIBeatmap createBeatmap() => new APIBeatmap
{
OnlineInfo = new APIBeatmap
FailTimes = new APIFailTimes
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
}
Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(),
}
};
}
@ -81,14 +78,11 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestOnlyFailMetrics()
{
AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo
AddStep("set beatmap", () => successRate.Beatmap = new APIBeatmap
{
OnlineInfo = new APIBeatmap
FailTimes = new APIFailTimes
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).ToArray(),
}
Fails = Enumerable.Range(1, 100).ToArray(),
}
});
@ -98,12 +92,9 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestEmptyMetrics()
{
AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo
AddStep("set beatmap", () => successRate.Beatmap = new APIBeatmap
{
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes(),
}
FailTimes = new APIFailTimes()
});
AddAssert("graph max values correct", () => successRate.ChildrenOfType<BarGraph>().All(graph => graph.MaxValue == 0));

View File

@ -9,7 +9,6 @@ using osu.Game.Beatmaps;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Resources;
using osuTK;
@ -69,24 +68,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => downloadButton.DownloadEnabled == enabled);
}
private BeatmapSetInfo createSoleily()
{
return new BeatmapSetInfo
{
ID = 1,
OnlineBeatmapSetID = 241526,
OnlineInfo = new APIBeatmapSet
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = false,
ExternalLink = string.Empty,
},
},
};
}
private void createButtonWithBeatmap(BeatmapSetInfo beatmap)
private void createButtonWithBeatmap(IBeatmapSetInfo beatmap)
{
AddStep("create button", () =>
{
@ -112,32 +94,47 @@ namespace osu.Game.Tests.Visual.Online
});
}
private BeatmapSetInfo getDownloadableBeatmapSet()
private IBeatmapSetInfo createSoleily()
{
var normal = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo;
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
return normal;
return new APIBeatmapSet
{
OnlineID = 241526,
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = false,
ExternalLink = string.Empty,
},
};
}
private BeatmapSetInfo getUndownloadableBeatmapSet()
private IBeatmapSetInfo getDownloadableBeatmapSet()
{
var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo;
beatmap.Metadata.Artist = "test";
beatmap.Metadata.Title = "undownloadable";
beatmap.Metadata.AuthorString = "test";
var apiBeatmapSet = CreateAPIBeatmapSet();
beatmap.OnlineInfo.HasVideo = true;
beatmap.OnlineInfo.HasStoryboard = true;
apiBeatmapSet.HasVideo = true;
apiBeatmapSet.HasStoryboard = true;
beatmap.OnlineInfo.Availability = new BeatmapSetOnlineAvailability
return apiBeatmapSet;
}
private IBeatmapSetInfo getUndownloadableBeatmapSet()
{
var apiBeatmapSet = CreateAPIBeatmapSet();
apiBeatmapSet.Artist = "test";
apiBeatmapSet.Title = "undownloadable";
apiBeatmapSet.AuthorString = "test";
apiBeatmapSet.HasVideo = true;
apiBeatmapSet.HasStoryboard = true;
apiBeatmapSet.Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
ExternalLink = "http://osu.ppy.sh",
};
return beatmap;
return apiBeatmapSet;
}
private class TestDownloadButton : BeatmapPanelDownloadButton
@ -146,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online
public DownloadState DownloadState => State.Value;
public TestDownloadButton(BeatmapSetInfo beatmapSet)
public TestDownloadButton(IBeatmapSetInfo beatmapSet)
: base(beatmapSet)
{
}

View File

@ -18,104 +18,25 @@ namespace osu.Game.Tests.Visual.Online
[Cached(typeof(IPreviewTrackOwner))]
public class TestSceneDirectPanel : OsuTestScene, IPreviewTrackOwner
{
private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo
{
OnlineBeatmapSetID = 123,
Metadata = new BeatmapMetadata
{
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new User
{
Username = "BanchoBot",
Id = 3,
},
},
OnlineInfo = new APIBeatmapSet
{
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
},
Beatmaps = new List<BeatmapInfo>
{
new BeatmapInfo
{
Ruleset = Ruleset.Value,
Version = "Test",
StarDifficulty = 6.42,
}
}
};
private BeatmapSetInfo getManyDifficultiesBeatmapSet(RulesetStore rulesets)
{
var beatmaps = new List<BeatmapInfo>();
for (int i = 0; i < 100; i++)
{
beatmaps.Add(new BeatmapInfo
{
Ruleset = rulesets.GetRuleset(i % 4),
StarDifficulty = 2 + i % 4 * 2,
BaseDifficulty = new BeatmapDifficulty
{
OverallDifficulty = 3.5f,
}
});
}
return new BeatmapSetInfo
{
OnlineBeatmapSetID = 1,
Metadata = new BeatmapMetadata
{
Title = "many difficulties beatmap",
Artist = "test",
Author = new User
{
Username = "BanchoBot",
Id = 3,
}
},
OnlineInfo = new APIBeatmapSet
{
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
},
Beatmaps = beatmaps,
};
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var normal = getBeatmapSet();
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
normal.HasVideo = true;
normal.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
var manyDifficulties = getManyDifficultiesBeatmapSet();
var explicitMap = getBeatmapSet();
explicitMap.OnlineInfo.HasExplicitContent = true;
explicitMap.HasExplicitContent = true;
var featuredMap = getBeatmapSet();
featuredMap.OnlineInfo.TrackId = 1;
featuredMap.TrackId = 1;
var explicitFeaturedMap = getBeatmapSet();
explicitFeaturedMap.OnlineInfo.HasExplicitContent = true;
explicitFeaturedMap.OnlineInfo.TrackId = 2;
explicitFeaturedMap.HasExplicitContent = true;
explicitFeaturedMap.TrackId = 2;
Child = new BasicScrollContainer
{
@ -145,7 +66,72 @@ namespace osu.Game.Tests.Visual.Online
},
};
BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
APIBeatmapSet getBeatmapSet() => CreateAPIBeatmapSet(Ruleset.Value);
APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet
{
OnlineID = 123,
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new User
{
Username = "BanchoBot",
Id = 3,
},
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new[]
{
new APIBeatmap
{
RulesetID = Ruleset.Value.ID ?? 0,
DifficultyName = "Test",
StarRating = 6.42,
}
}
};
APIBeatmapSet getManyDifficultiesBeatmapSet()
{
var beatmaps = new List<APIBeatmap>();
for (int i = 0; i < 100; i++)
{
beatmaps.Add(new APIBeatmap
{
RulesetID = i % 4,
StarRating = 2 + i % 4 * 2,
OverallDifficulty = 3.5f,
});
}
return new APIBeatmapSet
{
OnlineID = 1,
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new User
{
Username = "BanchoBot",
Id = 3,
},
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = beatmaps.ToArray(),
};
}
}
}
}

View File

@ -4,7 +4,7 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet.Buttons;
using osuTK;
@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online
[Test]
public void TestLoggedOutIn()
{
AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 });
AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 });
AddStep("log out", () => API.Logout());
checkEnabled(false);
AddStep("log in", () => API.Login("test", "test"));
@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Online
public void TestBeatmapChange()
{
AddStep("log in", () => API.Login("test", "test"));
AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 });
AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 });
checkEnabled(true);
AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo());
AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet());
checkEnabled(false);
}

View File

@ -26,7 +26,8 @@ namespace osu.Game.Tests.Visual.Online
{
LeaderboardModSelector modSelector;
FillFlowContainer<SpriteText> selectedMods;
var ruleset = new Bindable<RulesetInfo>();
var ruleset = new Bindable<IRulesetInfo>();
Add(selectedMods = new FillFlowContainer<SpriteText>
{

View File

@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Utils;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet.Scores;
@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Online
},
Mods = new[]
{
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
new OsuModFlashlight().Acronym,
new OsuModHardRock().Acronym,
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
new APIMod { Acronym = new OsuModHidden().Acronym },
new APIMod { Acronym = new OsuModFlashlight().Acronym },
new APIMod { Acronym = new OsuModHardRock().Acronym },
},
Rank = ScoreRank.XH,
PP = 200,
@ -86,9 +87,9 @@ namespace osu.Game.Tests.Visual.Online
},
Mods = new[]
{
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
new OsuModFlashlight().Acronym,
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
new APIMod { Acronym = new OsuModHidden().Acronym },
new APIMod { Acronym = new OsuModFlashlight().Acronym },
},
Rank = ScoreRank.S,
PP = 190,
@ -110,8 +111,8 @@ namespace osu.Game.Tests.Visual.Online
},
Mods = new[]
{
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
new APIMod { Acronym = new OsuModHidden().Acronym },
},
Rank = ScoreRank.B,
PP = 180,
@ -133,7 +134,7 @@ namespace osu.Game.Tests.Visual.Online
},
Mods = new[]
{
new OsuModDoubleTime().Acronym,
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
},
Rank = ScoreRank.C,
PP = 170,
@ -226,10 +227,10 @@ namespace osu.Game.Tests.Visual.Online
},
Mods = new[]
{
new OsuModDoubleTime().Acronym,
new OsuModHidden().Acronym,
new OsuModFlashlight().Acronym,
new OsuModHardRock().Acronym,
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
new APIMod { Acronym = new OsuModHidden().Acronym },
new APIMod { Acronym = new OsuModFlashlight().Acronym },
new APIMod { Acronym = new OsuModHardRock().Acronym },
},
Rank = ScoreRank.XH,
PP = 200,

View File

@ -2,16 +2,16 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Framework.Graphics;
using osu.Game.Scoring;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile.Sections.Ranks;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
@ -19,79 +19,79 @@ namespace osu.Game.Tests.Visual.Online
{
public TestSceneUserProfileScores()
{
var firstScore = new ScoreInfo
var firstScore = new APIScoreInfo
{
PP = 1047.21,
Rank = ScoreRank.SH,
BeatmapInfo = new BeatmapInfo
Beatmap = new APIBeatmap
{
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Title = "JUSTadICE (TV Size)",
Artist = "Oomori Seiko"
Artist = "Oomori Seiko",
},
Version = "Extreme"
DifficultyName = "Extreme"
},
Date = DateTimeOffset.Now,
Mods = new Mod[]
Mods = new[]
{
new OsuModHidden(),
new OsuModHardRock(),
new OsuModDoubleTime()
new APIMod { Acronym = new OsuModHidden().Acronym },
new APIMod { Acronym = new OsuModHardRock().Acronym },
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
},
Accuracy = 0.9813
};
var secondScore = new ScoreInfo
var secondScore = new APIScoreInfo
{
PP = 134.32,
Rank = ScoreRank.A,
BeatmapInfo = new BeatmapInfo
Beatmap = new APIBeatmap
{
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Title = "Triumph & Regret",
Artist = "typeMARS"
Artist = "typeMARS",
},
Version = "[4K] Regret"
DifficultyName = "[4K] Regret"
},
Date = DateTimeOffset.Now,
Mods = new Mod[]
Mods = new[]
{
new OsuModHardRock(),
new OsuModDoubleTime(),
new APIMod { Acronym = new OsuModHardRock().Acronym },
new APIMod { Acronym = new OsuModDoubleTime().Acronym },
},
Accuracy = 0.998546
};
var thirdScore = new ScoreInfo
var thirdScore = new APIScoreInfo
{
PP = 96.83,
Rank = ScoreRank.S,
BeatmapInfo = new BeatmapInfo
Beatmap = new APIBeatmap
{
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Title = "Idolize",
Artist = "Creo"
Artist = "Creo",
},
Version = "Insane"
DifficultyName = "Insane"
},
Date = DateTimeOffset.Now,
Accuracy = 0.9726
};
var noPPScore = new ScoreInfo
var noPPScore = new APIScoreInfo
{
Rank = ScoreRank.B,
BeatmapInfo = new BeatmapInfo
Beatmap = new APIBeatmap
{
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Title = "C18H27NO3(extend)",
Artist = "Team Grimoire"
Artist = "Team Grimoire",
},
Version = "[4K] Cataclysmic Hypernova"
DifficultyName = "[4K] Cataclysmic Hypernova"
},
Date = DateTimeOffset.Now,
Accuracy = 0.55879

View File

@ -5,6 +5,7 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select EZ mod", () =>
{
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
SelectedMods.Value = new[] { ruleset.CreateMod<ModEasy>() };
});
@ -105,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select HR mod", () =>
{
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
SelectedMods.Value = new[] { ruleset.CreateMod<ModHardRock>() };
});
@ -122,9 +123,9 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select unchanged Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>();
difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.BaseDifficulty);
difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty);
SelectedMods.Value = new[] { difficultyAdjustMod };
});
@ -141,9 +142,9 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select changed Difficulty Adjust mod", () =>
{
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance();
var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull();
var difficultyAdjustMod = ruleset.CreateMod<OsuModDifficultyAdjust>();
var originalDifficulty = advancedStats.BeatmapInfo.BaseDifficulty;
var originalDifficulty = advancedStats.BeatmapInfo.Difficulty;
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);
difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f;

View File

@ -31,154 +31,112 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestAllMetrics()
{
AddStep("all metrics", () => details.BeatmapInfo = new BeatmapInfo
AddStep("all metrics", () => details.BeatmapInfo = new APIBeatmap
{
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
}
},
Version = "All Metrics",
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Source = "osu!",
Tags = "this beatmap has all the metrics",
Ratings = Enumerable.Range(0, 11).ToArray(),
},
BaseDifficulty = new BeatmapDifficulty
DifficultyName = "All Metrics",
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
StarRating = 5.3f,
FailTimes = new APIFailTimes
{
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
StarDifficulty = 5.3f,
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
}
[Test]
public void TestAllMetricsExceptSource()
{
AddStep("all except source", () => details.BeatmapInfo = new BeatmapInfo
AddStep("all except source", () => details.BeatmapInfo = new APIBeatmap
{
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
}
},
Version = "All Metrics",
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Tags = "this beatmap has all the metrics",
Ratings = Enumerable.Range(0, 11).ToArray(),
},
BaseDifficulty = new BeatmapDifficulty
DifficultyName = "All Metrics",
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
StarRating = 5.3f,
FailTimes = new APIFailTimes
{
CircleSize = 7,
DrainRate = 1,
OverallDifficulty = 5.7f,
ApproachRate = 3.5f,
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
StarDifficulty = 5.3f,
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
}
[Test]
public void TestOnlyRatings()
{
AddStep("ratings", () => details.BeatmapInfo = new BeatmapInfo
AddStep("ratings", () => details.BeatmapInfo = new APIBeatmap
{
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
}
},
Version = "Only Ratings",
Metadata = new BeatmapMetadata
BeatmapSet = new APIBeatmapSet
{
Ratings = Enumerable.Range(0, 11).ToArray(),
Source = "osu!",
Tags = "this beatmap has ratings metrics but not retries or fails",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 6,
DrainRate = 9,
OverallDifficulty = 6,
ApproachRate = 6,
},
StarDifficulty = 4.8f,
DifficultyName = "Only Ratings",
CircleSize = 6,
DrainRate = 9,
OverallDifficulty = 6,
ApproachRate = 6,
StarRating = 4.8f,
});
}
[Test]
public void TestOnlyFailsAndRetries()
{
AddStep("fails retries", () => details.BeatmapInfo = new BeatmapInfo
AddStep("fails retries", () => details.BeatmapInfo = new APIBeatmap
{
Version = "Only Retries and Fails",
Metadata = new BeatmapMetadata
DifficultyName = "Only Retries and Fails",
BeatmapSet = new APIBeatmapSet
{
Source = "osu!",
Tags = "this beatmap has retries and fails but no ratings",
},
BaseDifficulty = new BeatmapDifficulty
CircleSize = 3.7f,
DrainRate = 6,
OverallDifficulty = 6,
ApproachRate = 7,
StarRating = 2.91f,
FailTimes = new APIFailTimes
{
CircleSize = 3.7f,
DrainRate = 6,
OverallDifficulty = 6,
ApproachRate = 7,
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
StarDifficulty = 2.91f,
OnlineInfo = new APIBeatmap
{
FailTimes = new APIFailTimes
{
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
},
}
});
}
[Test]
public void TestNoMetrics()
{
AddStep("no metrics", () => details.BeatmapInfo = new BeatmapInfo
AddStep("no metrics", () => details.BeatmapInfo = new APIBeatmap
{
Version = "No Metrics",
Metadata = new BeatmapMetadata
DifficultyName = "No Metrics",
BeatmapSet = new APIBeatmapSet
{
Source = "osu!",
Tags = "this beatmap has no metrics",
},
BaseDifficulty = new BeatmapDifficulty
{
CircleSize = 5,
DrainRate = 5,
OverallDifficulty = 5.5f,
ApproachRate = 6.5f,
},
StarDifficulty = 1.97f,
CircleSize = 5,
DrainRate = 5,
OverallDifficulty = 5.5f,
ApproachRate = 6.5f,
StarRating = 1.97f,
});
}
@ -191,9 +149,9 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestOnlineMetrics()
{
AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new BeatmapInfo
AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new APIBeatmap
{
OnlineBeatmapID = 162,
OnlineID = 162,
});
AddStep("set online", () => api.SetState(APIState.Online));
AddStep("set offline", () => api.SetState(APIState.Offline));

View File

@ -110,25 +110,19 @@ namespace osu.Game.Tests.Visual.UserInterface
base.Dispose(isDisposing);
}
private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo
private static readonly APIBeatmapSet beatmap_set = new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
Covers = new BeatmapSetOnlineCovers
{
Covers = new BeatmapSetOnlineCovers
{
Cover = "https://assets.ppy.sh/beatmaps/1094296/covers/cover@2x.jpg?1581416305"
}
Cover = "https://assets.ppy.sh/beatmaps/1094296/covers/cover@2x.jpg?1581416305"
}
};
private static readonly BeatmapSetInfo no_cover_beatmap_set = new BeatmapSetInfo
private static readonly APIBeatmapSet no_cover_beatmap_set = new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
Covers = new BeatmapSetOnlineCovers
{
Covers = new BeatmapSetOnlineCovers
{
Cover = string.Empty
}
Cover = string.Empty
}
};
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
@ -65,10 +66,10 @@ namespace osu.Game.Tests.Visual.UserInterface
private void createPaddedComponent(bool hasDescription = false, bool padded = true)
{
LabelledDrawable<Drawable> component = null;
AddStep("create component", () =>
{
LabelledDrawable<Drawable> component;
Child = new Container
{
Anchor = Anchor.Centre,
@ -81,6 +82,8 @@ namespace osu.Game.Tests.Visual.UserInterface
component.Label = "a sample component";
component.Description = hasDescription ? "this text describes the component" : string.Empty;
});
AddAssert($"description {(hasDescription ? "visible" : "hidden")}", () => component.ChildrenOfType<TextFlowContainer>().ElementAt(1).IsPresent == hasDescription);
}
private class PaddedLabelledDrawable : LabelledDrawable<Drawable>

View File

@ -12,6 +12,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
@ -24,7 +25,6 @@ namespace osu.Game.Tests.Visual.UserInterface
private BeatmapSetInfo testBeatmap;
private IAPIProvider api;
private RulesetStore rulesets;
[Resolved]
private BeatmapManager beatmaps { get; set; }
@ -33,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface
private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets)
{
this.api = api;
this.rulesets = rulesets;
testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result;
}
@ -81,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Child = background = new TestUpdateableBeatmapBackgroundSprite
{
RelativeSizeAxes = Axes.Both,
Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Response?.ToBeatmapSet(rulesets) } }
Beatmap = { Value = new APIBeatmap { BeatmapSet = req.Response } }
};
});

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{
AddStep("setup cover", () => Child = new UpdateableOnlineBeatmapSetCover(coverType)
{
OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo,
OnlineInfo = CreateAPIBeatmapSet(),
RelativeSizeAxes = Axes.Both,
Masking = true,
});
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("setup covers", () =>
{
BeatmapSetInfo setInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
var beatmapSet = CreateAPIBeatmapSet();
FillFlowContainer fillFlow;
@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var cover = new UpdateableOnlineBeatmapSetCover(coverType)
{
OnlineInfo = setInfo.OnlineInfo,
OnlineInfo = beatmapSet,
Height = 100,
Masking = true,
};
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover
{
OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo,
OnlineInfo = CreateAPIBeatmapSet(),
RelativeSizeAxes = Axes.Both,
Masking = true,
});
@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover(0)
{
OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg").OnlineInfo,
OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"),
RelativeSizeAxes = Axes.Both,
Masking = true,
Alpha = 0.4f
@ -128,16 +128,13 @@ namespace osu.Game.Tests.Visual.UserInterface
AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1);
AddStep("switch beatmap",
() => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg").OnlineInfo);
() => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg"));
AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType<OnlineBeatmapSetCover>().Except(new[] { initialCover }).Any());
}
private static BeatmapSetInfo createBeatmapWithCover(string coverUrl) => new BeatmapSetInfo
private static APIBeatmapSet createBeatmapWithCover(string coverUrl) => new APIBeatmapSet
{
OnlineInfo = new APIBeatmapSet
{
Covers = new BeatmapSetOnlineCovers { Cover = coverUrl }
}
Covers = new BeatmapSetOnlineCovers { Cover = coverUrl }
};
private class TestUpdateableOnlineBeatmapSetCover : UpdateableOnlineBeatmapSetCover

View File

@ -3,7 +3,7 @@
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="DeepEqual" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />

View File

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
</ItemGroup>

View File

@ -131,7 +131,7 @@ namespace osu.Game.Beatmaps
var localRulesetInfo = rulesetInfo as RulesetInfo;
// Difficulty can only be computed if the beatmap and ruleset are locally available.
if (localBeatmapInfo == null || localRulesetInfo == null)
if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null)
{
// If not, fall back to the existing star difficulty (e.g. from an online source).
return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0));

View File

@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
[Serializable]
public class BeatmapInfo : IEquatable<BeatmapInfo>, IHasPrimaryKey, IBeatmapInfo, IBeatmapOnlineInfo
public class BeatmapInfo : IEquatable<BeatmapInfo>, IHasPrimaryKey, IBeatmapInfo
{
public int ID { get; set; }
@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps
string IBeatmapInfo.DifficultyName => Version;
[JsonIgnore]
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata;
IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new BeatmapMetadata();
[JsonIgnore]
IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty;
@ -201,24 +201,5 @@ namespace osu.Game.Beatmaps
double IBeatmapInfo.StarRating => StarDifficulty;
#endregion
#region Implementation of IBeatmapOnlineInfo
[JsonIgnore]
public int CircleCount => OnlineInfo.CircleCount;
[JsonIgnore]
public int SliderCount => OnlineInfo.SliderCount;
[JsonIgnore]
public int PlayCount => OnlineInfo.PlayCount;
[JsonIgnore]
public int PassCount => OnlineInfo.PassCount;
[JsonIgnore]
public APIFailTimes FailTimes => OnlineInfo.FailTimes;
#endregion
}
}

View File

@ -11,14 +11,14 @@ namespace osu.Game.Beatmaps
/// <summary>
/// A user-presentable display title representing this beatmap.
/// </summary>
public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{getClosestMetadata(beatmapInfo)} {getVersionString(beatmapInfo)}".Trim();
public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata} {getVersionString(beatmapInfo)}".Trim();
/// <summary>
/// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields.
/// </summary>
public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo, bool includeDifficultyName = true, bool includeCreator = true)
{
var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable(includeCreator);
var metadata = beatmapInfo.Metadata.GetDisplayTitleRomanisable(includeCreator);
if (includeDifficultyName)
{
@ -32,12 +32,8 @@ namespace osu.Game.Beatmaps
public static string[] GetSearchableTerms(this IBeatmapInfo beatmapInfo) => new[]
{
beatmapInfo.DifficultyName
}.Concat(getClosestMetadata(beatmapInfo).GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
}.Concat(beatmapInfo.Metadata.GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
// temporary helper methods until we figure which metadata should be where.
private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) =>
beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet?.Metadata ?? new BeatmapMetadata();
}
}

View File

@ -114,7 +114,8 @@ namespace osu.Game.Beatmaps
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
/// <param name="beatmapSkin">The beatmap <see cref="ISkin"/> content to write, null if to be omitted.</param>
public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) => beatmapModelManager.Save(info, beatmapContent, beatmapSkin);
public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) =>
beatmapModelManager.Save(info, beatmapContent, beatmapSkin);
/// <summary>
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
@ -249,6 +250,23 @@ namespace osu.Game.Beatmaps
public IBindable<WeakReference<ArchiveDownloadRequest<BeatmapSetInfo>>> DownloadFailed => beatmapModelDownloader.DownloadFailed;
// Temporary method until this class supports IBeatmapSetInfo or otherwise.
public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false)
{
return beatmapModelDownloader.Download(new BeatmapSetInfo
{
OnlineBeatmapSetID = model.OnlineID,
Metadata = new BeatmapMetadata
{
Title = model.Metadata?.Title ?? string.Empty,
Artist = model.Metadata?.Artist ?? string.Empty,
TitleUnicode = model.Metadata?.TitleUnicode ?? string.Empty,
ArtistUnicode = model.Metadata?.ArtistUnicode ?? string.Empty,
Author = new User { Username = model.Metadata?.Author },
}
}, minimiseDownloadSize);
}
public bool Download(BeatmapSetInfo model, bool minimiseDownloadSize = false)
{
return beatmapModelDownloader.Download(model, minimiseDownloadSize);

View File

@ -9,6 +9,8 @@ using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Users;
#nullable enable
namespace osu.Game.Beatmaps
{
[ExcludeFromDynamicCompile]
@ -17,21 +19,21 @@ namespace osu.Game.Beatmaps
{
public int ID { get; set; }
public string Title { get; set; }
public string Title { get; set; } = string.Empty;
[JsonProperty("title_unicode")]
public string TitleUnicode { get; set; }
public string TitleUnicode { get; set; } = string.Empty;
public string Artist { get; set; }
public string Artist { get; set; } = string.Empty;
[JsonProperty("artist_unicode")]
public string ArtistUnicode { get; set; }
public string ArtistUnicode { get; set; } = string.Empty;
[JsonIgnore]
public List<BeatmapInfo> Beatmaps { get; set; }
public List<BeatmapInfo> Beatmaps { get; set; } = new List<BeatmapInfo>();
[JsonIgnore]
public List<BeatmapSetInfo> BeatmapSets { get; set; }
public List<BeatmapSetInfo> BeatmapSets { get; set; } = new List<BeatmapSetInfo>();
/// <summary>
/// Helper property to deserialize a username to <see cref="User"/>.
@ -55,7 +57,7 @@ namespace osu.Game.Beatmaps
[Column("Author")]
public string AuthorString
{
get => Author?.Username;
get => Author?.Username ?? string.Empty;
set
{
Author ??= new User();
@ -67,22 +69,22 @@ namespace osu.Game.Beatmaps
/// The author of the beatmaps in this set.
/// </summary>
[JsonIgnore]
public User Author;
public User? Author;
public string Source { get; set; }
public string Source { get; set; } = string.Empty;
[JsonProperty(@"tags")]
public string Tags { get; set; }
public string Tags { get; set; } = string.Empty;
/// <summary>
/// The time in milliseconds to begin playing the track for preview purposes.
/// If -1, the track should begin playing at 40% of its length.
/// </summary>
public int PreviewTime { get; set; }
public int PreviewTime { get; set; } = -1;
public string AudioFile { get; set; }
public string AudioFile { get; set; } = string.Empty;
public string BackgroundFile { get; set; }
public string BackgroundFile { get; set; } = string.Empty;
public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other);

View File

@ -216,7 +216,8 @@ namespace osu.Game.Beatmaps
var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo();
// metadata may have changed; update the path with the standard format.
beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu";
beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu");
beatmapInfo.MD5Hash = stream.ComputeMD5Hash();
// update existing or populate new file's filename.

View File

@ -6,10 +6,8 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Beatmaps
{
@ -32,13 +30,11 @@ namespace osu.Game.Beatmaps
public List<BeatmapInfo> Beatmaps { get; set; }
public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
[NotNull]
public List<BeatmapSetFileInfo> Files { get; set; } = new List<BeatmapSetFileInfo>();
// This field is temporary and only used by `APIBeatmapSet.ToBeatmapSet` (soon to be removed) and tests (to be updated to provide APIBeatmapSet instead).
[NotMapped]
public APIBeatmapSet OnlineInfo { get; set; }
/// <summary>
/// The maximum star difficulty of all beatmaps in this set.
/// </summary>
@ -100,80 +96,5 @@ namespace osu.Game.Beatmaps
IEnumerable<INamedFileUsage> IBeatmapSetInfo.Files => Files;
#endregion
#region Delegation for IBeatmapSetOnlineInfo
[NotMapped]
[JsonIgnore]
public DateTimeOffset Submitted => OnlineInfo.Submitted;
[NotMapped]
[JsonIgnore]
public DateTimeOffset? Ranked => OnlineInfo.Ranked;
[NotMapped]
[JsonIgnore]
public DateTimeOffset? LastUpdated => OnlineInfo.LastUpdated;
[JsonIgnore]
public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None;
[NotMapped]
[JsonIgnore]
public bool HasExplicitContent => OnlineInfo.HasExplicitContent;
[NotMapped]
[JsonIgnore]
public bool HasVideo => OnlineInfo.HasVideo;
[NotMapped]
[JsonIgnore]
public bool HasStoryboard => OnlineInfo.HasStoryboard;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineCovers Covers => OnlineInfo.Covers;
[NotMapped]
[JsonIgnore]
public string Preview => OnlineInfo.Preview;
[NotMapped]
[JsonIgnore]
public double BPM => OnlineInfo.BPM;
[NotMapped]
[JsonIgnore]
public int PlayCount => OnlineInfo.PlayCount;
[NotMapped]
[JsonIgnore]
public int FavouriteCount => OnlineInfo.FavouriteCount;
[NotMapped]
[JsonIgnore]
public bool HasFavourited => OnlineInfo.HasFavourited;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineAvailability Availability => OnlineInfo.Availability;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineGenre Genre => OnlineInfo.Genre;
[NotMapped]
[JsonIgnore]
public BeatmapSetOnlineLanguage Language => OnlineInfo.Language;
[NotMapped]
[JsonIgnore]
public int? TrackId => OnlineInfo?.TrackId;
[NotMapped]
[JsonIgnore]
public int[] Ratings => OnlineInfo?.Ratings;
#endregion
}
}

View File

@ -0,0 +1,280 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osuTK;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Resources.Localisation.Web;
using osuTK.Graphics;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class BeatmapCard : OsuClickableContainer
{
public const float TRANSITION_DURATION = 400;
private const float width = 408;
private const float height = 100;
private const float corner_radius = 10;
private readonly APIBeatmapSet beatmapSet;
private UpdateableOnlineBeatmapSetCover leftCover;
private FillFlowContainer iconArea;
private Container mainContent;
private BeatmapCardContentBackground mainContentBackground;
private GridContainer titleContainer;
private GridContainer artistContainer;
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
public BeatmapCard(APIBeatmapSet beatmapSet)
: base(HoverSampleSet.Submit)
{
this.beatmapSet = beatmapSet;
}
[BackgroundDependencyLoader]
private void load()
{
Width = width;
Height = height;
CornerRadius = corner_radius;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background3
},
new Container
{
Name = @"Left (icon) area",
Size = new Vector2(height),
Children = new Drawable[]
{
leftCover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List)
{
RelativeSizeAxes = Axes.Both,
OnlineInfo = beatmapSet
},
iconArea = new FillFlowContainer
{
Margin = new MarginPadding(5),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(1)
}
}
},
mainContent = new Container
{
Name = @"Main content",
X = height - corner_radius,
Height = height,
CornerRadius = corner_radius,
Masking = true,
Children = new Drawable[]
{
mainContentBackground = new BeatmapCardContentBackground(beatmapSet)
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Horizontal = 10,
Vertical = 4
},
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
titleContainer = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new[]
{
new OsuSpriteText
{
Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title),
Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
Truncate = true
},
Empty()
}
}
},
artistContainer = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new[]
{
new OsuSpriteText
{
Text = createArtistText(),
Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold),
RelativeSizeAxes = Axes.X,
Truncate = true
},
Empty()
},
}
},
new LinkFlowContainer(s =>
{
s.Shadow = false;
s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.Margin = new MarginPadding { Top = 2 };
d.AddText("mapped by ", t => t.Colour = colourProvider.Content2);
d.AddUserLink(beatmapSet.Author);
}),
}
},
new FillFlowContainer
{
Name = @"Bottom content",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding
{
Horizontal = 10,
Vertical = 4
},
Spacing = new Vector2(4, 0),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Children = new Drawable[]
{
new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
Status = beatmapSet.Status,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new DifficultySpectrumDisplay(beatmapSet)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
DotSize = new Vector2(6, 12)
}
}
}
}
}
};
if (beatmapSet.HasVideo)
iconArea.Add(new IconPill(FontAwesome.Solid.Film));
if (beatmapSet.HasStoryboard)
iconArea.Add(new IconPill(FontAwesome.Solid.Image));
if (beatmapSet.HasExplicitContent)
{
titleContainer.Content[0][1] = new ExplicitContentBeatmapPill
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding { Left = 5 }
};
}
if (beatmapSet.TrackId != null)
{
artistContainer.Content[0][1] = new FeaturedArtistBeatmapPill
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Margin = new MarginPadding { Left = 5 }
};
}
}
protected override void LoadComplete()
{
base.LoadComplete();
updateState();
FinishTransforms(true);
}
protected override bool OnHover(HoverEvent e)
{
updateState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateState();
base.OnHoverLost(e);
}
private LocalisableString createArtistText()
{
var romanisableArtist = new RomanisableString(beatmapSet.ArtistUnicode, beatmapSet.Artist);
return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist);
}
private void updateState()
{
float targetWidth = width - height;
if (IsHovered)
targetWidth -= 20;
mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint);
mainContentBackground.Dimmed.Value = IsHovered;
leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint);
}
}
}

View File

@ -0,0 +1,71 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable enable
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Overlays;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class BeatmapCardContentBackground : CompositeDrawable
{
public BindableBool Dimmed { get; } = new BindableBool();
private readonly Box background;
private readonly DelayedLoadUnloadWrapper cover;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
public BeatmapCardContentBackground(IBeatmapSetOnlineInfo onlineInfo)
{
InternalChildren = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
cover = new DelayedLoadUnloadWrapper(() => createCover(onlineInfo), 500, 500)
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.Transparent
}
};
}
private static Drawable createCover(IBeatmapSetOnlineInfo onlineInfo) => new OnlineBeatmapSetCover(onlineInfo)
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill
};
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
background.Colour = colourProvider.Background2;
}
protected override void LoadComplete()
{
base.LoadComplete();
Dimmed.BindValueChanged(_ => updateState(), true);
FinishTransforms(true);
}
private void updateState() => Schedule(() =>
{
background.FadeColour(Dimmed.Value ? colourProvider.Background4 : colourProvider.Background2, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
var gradient = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0), Colour4.White.Opacity(0.2f));
cover.FadeColour(gradient, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint);
});
}
}

View File

@ -60,8 +60,9 @@ namespace osu.Game.Beatmaps.Drawables
/// <param name="ruleset">The ruleset to show the difficulty with.</param>
/// <param name="mods">The mods to show the difficulty with.</param>
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true)
: this(beatmapInfo, shouldShowTooltip)
/// <param name="performBackgroundDifficultyLookup">Whether to perform difficulty lookup (including calculation if necessary).</param>
public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList<Mod> mods, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
: this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup)
{
this.ruleset = ruleset ?? beatmapInfo.Ruleset;
this.mods = mods ?? Array.Empty<Mod>();

View File

@ -57,12 +57,7 @@ namespace osu.Game.Beatmaps.Drawables
return new OnlineBeatmapSetCover(online, beatmapSetCoverType);
if (model is BeatmapInfo localModel)
{
if (localModel.BeatmapSet?.OnlineInfo != null)
return new OnlineBeatmapSetCover(localModel.BeatmapSet.OnlineInfo, beatmapSetCoverType);
return new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(localModel));
}
return new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap);
}

View File

@ -82,7 +82,7 @@ namespace osu.Game.Beatmaps.Formats
{
writer.WriteLine("[General]");
if (beatmap.Metadata.AudioFile != null) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}"));
@ -126,13 +126,13 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine("[Metadata]");
writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}"));
if (beatmap.Metadata.TitleUnicode != null) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.TitleUnicode)) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
if (beatmap.Metadata.ArtistUnicode != null) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
if (!string.IsNullOrEmpty(beatmap.Metadata.ArtistUnicode)) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}"));
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
if (beatmap.Metadata.Source != null) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
if (beatmap.Metadata.Tags != null) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
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 (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}"));
if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}"));
}

View File

@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps.Formats
protected override void ParseStreamInto(LineBufferedReader stream, T output)
{
Section section = Section.None;
Section section = Section.General;
string line;
@ -47,10 +47,7 @@ namespace osu.Game.Beatmaps.Formats
if (line.StartsWith('[') && line.EndsWith(']'))
{
if (!Enum.TryParse(line[1..^1], out section))
{
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
section = Section.None;
}
OnBeginNewSection(section);
continue;
@ -148,7 +145,6 @@ namespace osu.Game.Beatmaps.Formats
protected enum Section
{
None,
General,
Editor,
Metadata,

View File

@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps
/// <summary>
/// The metadata representing this beatmap. May be shared between multiple beatmaps.
/// </summary>
IBeatmapMetadataInfo? Metadata { get; }
IBeatmapMetadataInfo Metadata { get; }
/// <summary>
/// The difficulty settings for this beatmap.

View File

@ -149,7 +149,7 @@ namespace osu.Game.Beatmaps
protected override Texture GetBackground()
{
if (Metadata?.BackgroundFile == null)
if (string.IsNullOrEmpty(Metadata?.BackgroundFile))
return null;
try
@ -165,7 +165,7 @@ namespace osu.Game.Beatmaps
protected override Track GetBeatmapTrack()
{
if (Metadata?.AudioFile == null)
if (string.IsNullOrEmpty(Metadata?.AudioFile))
return null;
try
@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps
protected override Waveform GetWaveform()
{
if (Metadata?.AudioFile == null)
if (string.IsNullOrEmpty(Metadata?.AudioFile))
return null;
try

View File

@ -466,7 +466,7 @@ namespace osu.Game.Database
if (retrievedItem == null)
throw new ArgumentException(@"Specified model could not be found", nameof(item));
string filename = $"{getValidFilename(item.ToString())}{HandledExtensions.First()}";
string filename = $"{GetValidFilename(item.ToString())}{HandledExtensions.First()}";
using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create))
ExportModelTo(retrievedItem, stream);
@ -913,9 +913,15 @@ namespace osu.Game.Database
return Guid.NewGuid().ToString();
}
private string getValidFilename(string filename)
private readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars()
// Backslash is added to avoid issues when exporting to zip.
// See SharpCompress filename normalisation https://github.com/adamhathcock/sharpcompress/blob/a1e7c0068db814c9aa78d86a94ccd1c761af74bd/src/SharpCompress/Writers/Zip/ZipWriter.cs#L143.
.Append('\\')
.ToArray();
protected string GetValidFilename(string filename)
{
foreach (char c in Path.GetInvalidFileNameChars())
foreach (char c in invalidFilenameCharacters)
filename = filename.Replace(c, '_');
return filename;
}

View File

@ -7,12 +7,10 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Framework.Platform;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
namespace osu.Game.Graphics.Containers
@ -58,23 +56,14 @@ namespace osu.Game.Graphics.Containers
AddText(text.Substring(previousLinkEnd));
}
public void AddLink(string text, string url, Action<SpriteText> creationParameters = null) =>
public void AddLink(LocalisableString text, string url, Action<SpriteText> creationParameters = null) =>
createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.External, url), url);
public void AddLink(string text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
public void AddLink(LocalisableString text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action);
public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText);
public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
{
var spriteText = new OsuSpriteText { Text = text };
AddText(spriteText, creationParameters);
RemoveInternal(spriteText); // TODO: temporary, will go away when TextParts support localisation properly.
createLink(new TextPartManual(spriteText.Yield()), new LinkDetails(action, argument), tooltipText);
}
=> createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText);
public void AddLink(IEnumerable<SpriteText> text, LinkAction action, string linkArgument, string tooltipText = null)
{

View File

@ -8,6 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
namespace osu.Game.Graphics.UserInterface
@ -19,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface
/// </summary>
protected virtual bool PlaySoundsOnUserChange => true;
public string LabelText
public LocalisableString LabelText
{
set
{

View File

@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osuTK;
@ -156,18 +157,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
descriptionText.Colour = osuColour.Yellow;
}
public string Label
public LocalisableString Label
{
set => labelText.Text = value;
}
public string Description
public LocalisableString Description
{
set
{
descriptionText.Text = value;
if (!string.IsNullOrEmpty(value))
if (!string.IsNullOrEmpty(value.ToString()))
descriptionText.Show();
else
descriptionText.Hide();

View File

@ -107,7 +107,8 @@ namespace osu.Game.Online.API
WebRequest = CreateWebRequest();
WebRequest.Failed += Fail;
WebRequest.AllowRetryOnTimeout = false;
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
if (!string.IsNullOrEmpty(API.AccessToken))
WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}");
if (isFailing) return;

View File

@ -14,15 +14,15 @@ namespace osu.Game.Online.API.Requests
{
public class GetScoresRequest : APIRequest<APIScoresCollection>
{
private readonly BeatmapInfo beatmapInfo;
private readonly IBeatmapInfo beatmapInfo;
private readonly BeatmapLeaderboardScope scope;
private readonly RulesetInfo ruleset;
private readonly IRulesetInfo ruleset;
private readonly IEnumerable<IMod> mods;
public GetScoresRequest(BeatmapInfo beatmapInfo, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null)
public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null)
{
if (!beatmapInfo.OnlineBeatmapID.HasValue)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
if (beatmapInfo.OnlineID <= 0)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(IBeatmapInfo.OnlineID)}.");
if (scope == BeatmapLeaderboardScope.Local)
throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard");
@ -33,7 +33,7 @@ namespace osu.Game.Online.API.Requests
this.mods = mods ?? Array.Empty<IMod>();
}
protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}";
protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}";
private string createQueryParameters()
{

View File

@ -43,16 +43,16 @@ namespace osu.Game.Online.API.Requests.Responses
public double StarRating { get; set; }
[JsonProperty(@"drain")]
private float drainRate { get; set; }
public float DrainRate { get; set; }
[JsonProperty(@"cs")]
private float circleSize { get; set; }
public float CircleSize { get; set; }
[JsonProperty(@"ar")]
private float approachRate { get; set; }
public float ApproachRate { get; set; }
[JsonProperty(@"accuracy")]
private float overallDifficulty { get; set; }
public float OverallDifficulty { get; set; }
[JsonIgnore]
public double Length { get; set; }
@ -100,10 +100,10 @@ namespace osu.Game.Online.API.Requests.Responses
MaxCombo = MaxCombo,
BaseDifficulty = new BeatmapDifficulty
{
DrainRate = drainRate,
CircleSize = circleSize,
ApproachRate = approachRate,
OverallDifficulty = overallDifficulty,
DrainRate = DrainRate,
CircleSize = CircleSize,
ApproachRate = ApproachRate,
OverallDifficulty = OverallDifficulty,
},
OnlineInfo = this,
};
@ -115,10 +115,10 @@ namespace osu.Game.Online.API.Requests.Responses
public IBeatmapDifficultyInfo Difficulty => new BeatmapDifficulty
{
DrainRate = drainRate,
CircleSize = circleSize,
ApproachRate = approachRate,
OverallDifficulty = overallDifficulty,
DrainRate = DrainRate,
CircleSize = CircleSize,
ApproachRate = ApproachRate,
OverallDifficulty = OverallDifficulty,
};
IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet;

View File

@ -119,7 +119,7 @@ namespace osu.Game.Online.API.Requests.Responses
public string Tags { get; set; } = string.Empty;
[JsonProperty(@"beatmaps")]
public IEnumerable<APIBeatmap> Beatmaps { get; set; } = Array.Empty<APIBeatmap>();
public APIBeatmap[] Beatmaps { get; set; } = Array.Empty<APIBeatmap>();
public virtual BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets)
{
@ -128,7 +128,6 @@ namespace osu.Game.Online.API.Requests.Responses
OnlineBeatmapSetID = OnlineID,
Metadata = metadata,
Status = Status,
OnlineInfo = this
};
beatmapSet.Beatmaps = Beatmaps.Select(b =>

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using osu.Game.Beatmaps;
@ -36,6 +37,7 @@ namespace osu.Game.Online.API.Requests.Responses
public DateTimeOffset Date { get; set; }
[JsonProperty(@"beatmap")]
[CanBeNull]
public APIBeatmap Beatmap { get; set; }
[JsonProperty("accuracy")]
@ -45,6 +47,7 @@ namespace osu.Game.Online.API.Requests.Responses
public double? PP { get; set; }
[JsonProperty(@"beatmapset")]
[CanBeNull]
public APIBeatmapSet BeatmapSet
{
set
@ -62,10 +65,13 @@ namespace osu.Game.Online.API.Requests.Responses
public Dictionary<string, int> Statistics { get; set; }
[JsonProperty(@"mode_int")]
public int OnlineRulesetID { get; set; }
public int RulesetID { get; set; }
[JsonProperty(@"mods")]
public string[] Mods { get; set; }
private string[] mods { set => Mods = value.Select(acronym => new APIMod { Acronym = acronym }); }
[NotNull]
public IEnumerable<APIMod> Mods { get; set; } = Array.Empty<APIMod>();
[JsonProperty("rank")]
[JsonConverter(typeof(StringEnumConverter))]
@ -79,30 +85,30 @@ namespace osu.Game.Online.API.Requests.Responses
/// <returns></returns>
public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null)
{
var ruleset = rulesets.GetRuleset(OnlineRulesetID);
var ruleset = rulesets.GetRuleset(RulesetID);
var rulesetInstance = ruleset.CreateInstance();
var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty<Mod>();
var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray();
// all API scores provided by this class are considered to be legacy.
mods = mods.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
modInstances = modInstances.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
var scoreInfo = new ScoreInfo
{
TotalScore = TotalScore,
MaxCombo = MaxCombo,
BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets),
BeatmapInfo = Beatmap?.ToBeatmapInfo(rulesets),
User = User,
Accuracy = Accuracy,
OnlineScoreID = OnlineID,
Date = Date,
PP = PP,
RulesetID = OnlineRulesetID,
RulesetID = RulesetID,
Hash = HasReplay ? "online" : string.Empty, // todo: temporary?
Rank = Rank,
Ruleset = ruleset,
Mods = mods,
Mods = modInstances,
};
if (beatmap != null)
@ -144,7 +150,7 @@ namespace osu.Game.Online.API.Requests.Responses
return scoreInfo;
}
public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID };
public IRulesetInfo Ruleset => new RulesetInfo { ID = RulesetID };
IBeatmapInfo IScoreInfo.Beatmap => Beatmap;
}

View File

@ -17,6 +17,7 @@ using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer.Queueing;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Rulesets;
@ -148,6 +149,10 @@ namespace osu.Game.Online.Multiplayer
{
Room = joinedRoom;
APIRoom = room;
Debug.Assert(LocalUser != null);
addUserToAPIRoom(LocalUser);
foreach (var user in joinedRoom.Users)
updateUserPlayingState(user.UserID, user.State);
@ -358,6 +363,8 @@ namespace osu.Game.Online.Multiplayer
Room.Users.Add(user);
addUserToAPIRoom(user);
UserJoined?.Invoke(user);
RoomUpdated?.Invoke();
});
@ -377,6 +384,18 @@ namespace osu.Game.Online.Multiplayer
return handleUserLeft(user, UserKicked);
}
private void addUserToAPIRoom(MultiplayerRoomUser user)
{
Debug.Assert(APIRoom != null);
APIRoom.RecentParticipants.Add(user.User ?? new User
{
Id = user.UserID,
Username = "[Unresolved]"
});
APIRoom.ParticipantCount.Value++;
}
private Task handleUserLeft(MultiplayerRoomUser user, Action<MultiplayerRoomUser>? callback)
{
if (Room == null)
@ -390,6 +409,10 @@ namespace osu.Game.Online.Multiplayer
Room.Users.Remove(user);
PlayingUserIds.Remove(user.UserID);
Debug.Assert(APIRoom != null);
APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID);
APIRoom.ParticipantCount.Value--;
callback?.Invoke(user);
RoomUpdated?.Invoke();
}, false);
@ -667,20 +690,17 @@ namespace osu.Game.Online.Multiplayer
CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId);
}, cancellationToken);
/// <summary>
/// Retrieves a <see cref="BeatmapSetInfo"/> from an online source.
/// </summary>
/// <param name="beatmapId">The beatmap set ID.</param>
/// <param name="cancellationToken">A token to cancel the request.</param>
/// <returns>The <see cref="BeatmapSetInfo"/> retrieval task.</returns>
protected abstract Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default);
private async Task<PlaylistItem> createPlaylistItem(APIPlaylistItem item)
{
var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false);
var beatmap = set.Beatmaps.Single(b => b.OnlineBeatmapID == item.BeatmapID);
beatmap.MD5Hash = item.BeatmapChecksum;
// The incoming response is deserialised without circular reference handling currently.
// Because we require using metadata from this instance, populate the nested beatmaps' sets manually here.
foreach (var b in set.Beatmaps)
b.BeatmapSet = set;
var beatmap = set.Beatmaps.Single(b => b.OnlineID == item.BeatmapID);
beatmap.Checksum = item.BeatmapChecksum;
var ruleset = Rulesets.GetRuleset(item.RulesetID);
var rulesetInstance = ruleset.CreateInstance();
@ -699,6 +719,14 @@ namespace osu.Game.Online.Multiplayer
return playlistItem;
}
/// <summary>
/// Retrieves a <see cref="APIBeatmapSet"/> from an online source.
/// </summary>
/// <param name="beatmapId">The beatmap set ID.</param>
/// <param name="cancellationToken">A token to cancel the request.</param>
/// <returns>The <see cref="APIBeatmapSet"/> retrieval task.</returns>
protected abstract Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default);
/// <summary>
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.
/// </summary>

View File

@ -9,9 +9,9 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
namespace osu.Game.Online.Multiplayer
@ -167,9 +167,9 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), item);
}
protected override Task<BeatmapSetInfo> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
protected override Task<APIBeatmapSet> GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default)
{
var tcs = new TaskCompletionSource<BeatmapSetInfo>();
var tcs = new TaskCompletionSource<APIBeatmapSet>();
var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId);
req.Success += res =>
@ -180,7 +180,7 @@ namespace osu.Game.Online.Multiplayer
return;
}
tcs.SetResult(res.ToBeatmapSet(Rulesets));
tcs.SetResult(res);
};
req.Failure += e => tcs.SetException(e);

View File

@ -3,6 +3,7 @@
using System;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
@ -12,7 +13,7 @@ namespace osu.Game.Online.Placeholders
{
public Action Action;
public ClickablePlaceholder(string actionMessage, IconUsage icon)
public ClickablePlaceholder(LocalisableString actionMessage, IconUsage icon)
{
OsuTextFlowContainer textFlow;

View File

@ -3,14 +3,15 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
namespace osu.Game.Online.Placeholders
{
public class MessagePlaceholder : Placeholder
{
private readonly string message;
private readonly LocalisableString message;
public MessagePlaceholder(string message)
public MessagePlaceholder(LocalisableString message)
{
AddIcon(FontAwesome.Solid.ExclamationCircle, cp =>
{

View File

@ -7,6 +7,7 @@ using System.Linq;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
@ -61,7 +62,7 @@ namespace osu.Game.Online.Rooms
[CanBeNull]
public MultiplayerScoresAround ScoresAround { get; set; }
public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem)
public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem, [NotNull] BeatmapInfo beatmap)
{
var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance();
@ -70,7 +71,7 @@ namespace osu.Game.Online.Rooms
OnlineScoreID = ID,
TotalScore = TotalScore,
MaxCombo = MaxCombo,
BeatmapInfo = playlistItem.Beatmap.Value,
BeatmapInfo = beatmap,
BeatmapInfoID = playlistItem.BeatmapID,
Ruleset = playlistItem.Ruleset.Value,
RulesetID = playlistItem.RulesetID,

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -52,8 +53,13 @@ namespace osu.Game.Online.Rooms
downloadTracker?.RemoveAndDisposeImmediately();
Debug.Assert(item.NewValue.Beatmap.Value.BeatmapSet != null);
downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet);
downloadTracker.State.BindValueChanged(_ => updateAvailability());
AddInternal(downloadTracker);
downloadTracker.State.BindValueChanged(_ => updateAvailability(), true);
downloadTracker.Progress.BindValueChanged(_ =>
{
if (downloadTracker.State.Value != DownloadState.Downloading)
@ -63,9 +69,7 @@ namespace osu.Game.Online.Rooms
// we don't want to flood the network with this, so rate limit how often we send progress updates.
if (progressUpdate?.Completed != false)
progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500);
});
AddInternal(downloadTracker);
}, true);
}, true);
}

View File

@ -31,7 +31,7 @@ namespace osu.Game.Online.Rooms
public bool Expired { get; set; }
[JsonIgnore]
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
[JsonIgnore]
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
@ -65,13 +65,13 @@ namespace osu.Game.Online.Rooms
public PlaylistItem()
{
Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineBeatmapID ?? 0);
Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1);
Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.ID ?? 0);
}
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
public void MapObjects(RulesetStore rulesets)
{
Beatmap.Value ??= apiBeatmap.ToBeatmapInfo(rulesets);
Beatmap.Value ??= apiBeatmap;
Ruleset.Value ??= rulesets.GetRuleset(RulesetID);
Ruleset rulesetInstance = Ruleset.Value.CreateInstance();

View File

@ -5,6 +5,7 @@ using System.Net.Http;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
using osu.Game.Online.API;
using osu.Game.Online.Solo;
using osu.Game.Scoring;
namespace osu.Game.Online.Rooms
@ -14,14 +15,14 @@ namespace osu.Game.Online.Rooms
private readonly long scoreId;
private readonly long roomId;
private readonly long playlistItemId;
private readonly ScoreInfo scoreInfo;
private readonly SubmittableScore score;
public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo)
{
this.scoreId = scoreId;
this.roomId = roomId;
this.playlistItemId = playlistItemId;
this.scoreInfo = scoreInfo;
score = new SubmittableScore(scoreInfo);
}
protected override WebRequest CreateWebRequest()
@ -31,7 +32,7 @@ namespace osu.Game.Online.Rooms
req.ContentType = "application/json";
req.Method = HttpMethod.Put;
req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings
req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}));

View File

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

View File

@ -436,11 +436,15 @@ namespace osu.Game
/// <item>first beatmap from any ruleset.</item>
/// </list>
/// </remarks>
public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate<BeatmapInfo> difficultyCriteria = null)
public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate<BeatmapInfo> difficultyCriteria = null)
{
var databasedSet = beatmap.OnlineBeatmapSetID != null
? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
BeatmapSetInfo databasedSet = null;
if (beatmap.OnlineID > 0)
databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineID);
if (beatmap is BeatmapSetInfo localBeatmap)
databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash);
if (databasedSet == null)
{

View File

@ -5,7 +5,6 @@ using System;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
@ -135,7 +134,16 @@ namespace osu.Game.Overlays.AccountCreation
characterCheckText = passwordDescription.AddText("8 characters long");
passwordDescription.AddText(". Choose something long but also something you will remember, like a line from your favourite song.");
passwordTextBox.Current.ValueChanged += password => { characterCheckText.Drawables.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); };
passwordTextBox.Current.BindValueChanged(_ => updateCharacterCheckTextColour(), true);
characterCheckText.DrawablePartsRecreated += _ => updateCharacterCheckTextColour();
}
private void updateCharacterCheckTextColour()
{
string password = passwordTextBox.Text;
foreach (var d in characterCheckText.Drawables)
d.Colour = password.Length == 0 ? Color4.White : Interpolation.ValueAt(password.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In);
}
public override void OnEntering(IScreen last)

View File

@ -12,9 +12,9 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Resources.Localisation.Web;
using osuTK;
@ -206,7 +206,7 @@ namespace osu.Game.Overlays.BeatmapListing
getSetsRequest.Success += response =>
{
var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList();
var sets = response.BeatmapSets.ToList();
// If the previous request returned a null cursor, the API is indicating we can't paginate further (maybe there are no more beatmaps left).
if (sets.Count == 0 || response.Cursor == null)
@ -289,7 +289,7 @@ namespace osu.Game.Overlays.BeatmapListing
/// Contains the beatmap sets returned from API.
/// Valid for read if and only if <see cref="Type"/> is <see cref="SearchResultType.ResultsReturned"/>.
/// </summary>
public List<BeatmapSetInfo> Results { get; private set; }
public List<APIBeatmapSet> Results { get; private set; }
/// <summary>
/// Contains the names of supporter-only filters requested by the user.
@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapListing
/// </summary>
public List<LocalisableString> SupporterOnlyFiltersUsed { get; private set; }
public static SearchResult ResultsReturned(List<BeatmapSetInfo> results) => new SearchResult
public static SearchResult ResultsReturned(List<APIBeatmapSet> results) => new SearchResult
{
Type = SearchResultType.ResultsReturned,
Results = results

View File

@ -8,12 +8,12 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets;
using osu.Game.Scoring;
@ -49,17 +49,17 @@ namespace osu.Game.Overlays.BeatmapListing
public Bindable<SearchExplicit> ExplicitContent => explicitContentFilter.Current;
public BeatmapSetInfo BeatmapSet
public APIBeatmapSet BeatmapSet
{
set
{
if (value == null || string.IsNullOrEmpty(value.OnlineInfo.Covers.Cover))
if (value == null || string.IsNullOrEmpty(value.Covers.Cover))
{
beatmapCover.FadeOut(600, Easing.OutQuint);
return;
}
beatmapCover.OnlineInfo = value.OnlineInfo;
beatmapCover.OnlineInfo = value;
beatmapCover.FadeTo(0.1f, 200, Easing.OutQuint);
}
}

View File

@ -23,6 +23,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
using osuTK.Graphics;
@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu
{
public readonly BeatmapSetInfo SetInfo;
public readonly APIBeatmapSet SetInfo;
private const double hover_transition_time = 400;
private const int maximum_difficulty_icons = 10;
@ -49,10 +50,10 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
protected Action ViewBeatmap;
protected BeatmapPanel(BeatmapSetInfo setInfo)
protected BeatmapPanel(APIBeatmapSet setInfo)
: base(HoverSampleSet.Submit)
{
Debug.Assert(setInfo.OnlineBeatmapSetID != null);
Debug.Assert(setInfo.OnlineID > 0);
SetInfo = setInfo;
}
@ -95,8 +96,8 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
Action = ViewBeatmap = () =>
{
Debug.Assert(SetInfo.OnlineBeatmapSetID != null);
beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value);
Debug.Assert(SetInfo.OnlineID > 0);
beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineID);
};
}
@ -146,14 +147,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
{
var icons = new List<DifficultyIcon>();
if (SetInfo.Beatmaps.Count > maximum_difficulty_icons)
if (SetInfo.Beatmaps.Length > maximum_difficulty_icons)
{
foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.Where(b => b.RulesetID == ruleset.OnlineID).ToList(), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));
}
else
{
foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.Ruleset.ID).ThenBy(beatmap => beatmap.StarDifficulty))
foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.RulesetID).ThenBy(beatmap => beatmap.StarRating))
icons.Add(new DifficultyIcon(b));
}
@ -163,7 +164,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
protected Drawable CreateBackground() => new UpdateableOnlineBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
OnlineInfo = SetInfo.OnlineInfo,
OnlineInfo = SetInfo,
};
public class Statistic : FillFlowContainer

View File

@ -11,6 +11,7 @@ using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.BeatmapListing.Panels
{
@ -21,7 +22,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
/// <summary>
/// Currently selected beatmap. Used to present the correct difficulty after completing a download.
/// </summary>
public readonly IBindable<BeatmapInfo> SelectedBeatmap = new Bindable<BeatmapInfo>();
public readonly IBindable<APIBeatmap> SelectedBeatmap = new Bindable<APIBeatmap>();
private readonly ShakeContainer shakeContainer;
private readonly DownloadButton button;
@ -31,9 +32,9 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
private readonly BeatmapSetInfo beatmapSet;
private readonly IBeatmapSetInfo beatmapSet;
public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet)
public BeatmapPanelDownloadButton(IBeatmapSetInfo beatmapSet)
{
this.beatmapSet = beatmapSet;
@ -79,7 +80,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
case DownloadState.LocallyAvailable:
Predicate<BeatmapInfo> findPredicate = null;
if (SelectedBeatmap.Value != null)
findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineBeatmapID;
findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineID;
game?.PresentBeatmap(beatmapSet, findPredicate);
break;
@ -100,7 +101,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
break;
default:
if (beatmapSet.OnlineInfo?.Availability.DownloadDisabled ?? false)
if ((beatmapSet as IBeatmapSetOnlineInfo)?.Availability.DownloadDisabled == true)
{
button.Enabled.Value = false;
button.TooltipText = "this beatmap is currently not available for download.";

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