1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 18:42:56 +08:00

Merge branch 'master' into leaderboard-score-timeref

This commit is contained in:
Dean Herbert 2022-02-21 12:11:10 +09:00 committed by GitHub
commit abe1a3990a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 841 additions and 599 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.214.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.217.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

@ -10,10 +10,10 @@ namespace osu.Game.Rulesets.Mania.Difficulty
public class ManiaDifficultyAttributes : DifficultyAttributes
{
/// <summary>
/// The perceived hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
/// The hit window for a GREAT hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
/// <remarks>
/// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing.
/// Rate-adjusting mods do not affect the hit window at all in osu-stable.
/// </remarks>
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }

View File

@ -48,7 +48,9 @@ namespace osu.Game.Rulesets.Mania.Difficulty
{
StarRating = skills[0].DifficultyValue() * star_scaling_factor,
Mods = mods,
GreatHitWindow = Math.Ceiling(getHitWindow300(mods) / clockRate),
// In osu-stable mania, rate-adjustment mods don't affect the hit window.
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
ScoreMultiplier = getScoreMultiplier(mods),
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
};
@ -108,7 +110,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
}
}
private int getHitWindow300(Mod[] mods)
private double getHitWindow300(Mod[] mods)
{
if (isForCurrentRuleset)
{
@ -121,19 +123,14 @@ namespace osu.Game.Rulesets.Mania.Difficulty
return applyModAdjustments(47, mods);
static int applyModAdjustments(double value, Mod[] mods)
static double applyModAdjustments(double value, Mod[] mods)
{
if (mods.Any(m => m is ManiaModHardRock))
value /= 1.4;
else if (mods.Any(m => m is ManiaModEasy))
value *= 1.4;
if (mods.Any(m => m is ManiaModDoubleTime))
value *= 1.5;
else if (mods.Any(m => m is ManiaModHalfTime))
value *= 0.75;
return (int)value;
return value;
}
}

View File

@ -77,9 +77,6 @@ namespace osu.Game.Rulesets.Mania.Difficulty
private double computeDifficultyValue()
{
if (Attributes.ScoreMultiplier <= 0)
return 0;
double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);

View File

@ -147,7 +147,8 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("player score matching expected bonus score", () =>
{
double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value;
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
});

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
@ -12,6 +13,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Framework.Testing;
@ -21,6 +23,8 @@ using osu.Game.Database;
using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Tests.Resources;
@ -53,6 +57,25 @@ namespace osu.Game.Tests.Online
[SetUp]
public void SetUp() => Schedule(() =>
{
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case GetBeatmapsRequest beatmapsReq:
var beatmap = CreateAPIBeatmap();
beatmap.OnlineID = testBeatmapInfo.OnlineID;
beatmap.OnlineBeatmapSetID = testBeatmapSet.OnlineID;
beatmap.Checksum = testBeatmapInfo.MD5Hash;
beatmap.BeatmapSet!.OnlineID = testBeatmapSet.OnlineID;
beatmapsReq.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = new List<APIBeatmap> { beatmap } });
return true;
default:
return false;
}
};
beatmaps.AllowImport = new TaskCompletionSource<bool>();
testBeatmapFile = TestResources.GetQuickTestBeatmapForImport();
@ -63,18 +86,35 @@ namespace osu.Game.Tests.Online
Realm.Write(r => r.RemoveAll<BeatmapSetInfo>());
Realm.Write(r => r.RemoveAll<BeatmapInfo>());
selectedItem.Value = new PlaylistItem
selectedItem.Value = new PlaylistItem(testBeatmapInfo)
{
Beatmap = { Value = testBeatmapInfo },
RulesetID = testBeatmapInfo.Ruleset.OnlineID,
};
Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = selectedItem, }
};
recreateChildren();
});
private void recreateChildren()
{
var beatmapLookupCache = new BeatmapLookupCache();
Child = new DependencyProvidingContainer
{
CachedDependencies = new[]
{
(typeof(BeatmapLookupCache), (object)beatmapLookupCache)
},
Children = new Drawable[]
{
beatmapLookupCache,
availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = selectedItem, }
}
}
};
}
[Test]
public void TestBeatmapDownloadingFlow()
{
@ -123,10 +163,7 @@ namespace osu.Game.Tests.Online
});
addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded);
AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
{
SelectedItem = { BindTarget = selectedItem }
});
AddStep("recreate tracker", recreateChildren);
addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded);
AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely());
@ -167,7 +204,8 @@ namespace osu.Game.Tests.Online
public Live<BeatmapSetInfo> CurrentImport { get; private set; }
public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null)
public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore<byte[]> resources,
GameHost host = null, WorkingBeatmap defaultBeatmap = null)
: base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap)
{
}

View File

@ -3,6 +3,7 @@
using System;
using NUnit.Framework;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
namespace osu.Game.Tests.OnlinePlay
@ -29,9 +30,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, PlaylistOrder = 1 },
new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, PlaylistOrder = 2 },
new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
@ -47,9 +48,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 },
new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 },
new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, PlaylistOrder = 2 },
new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, PlaylistOrder = 1 },
new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
@ -65,9 +66,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 },
new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
new PlaylistItem(new APIBeatmap { OnlineID = 1003 }) { ID = 3, PlaylistOrder = 3 },
};
Assert.Multiple(() =>
@ -83,9 +84,9 @@ namespace osu.Game.Tests.OnlinePlay
{
var items = new[]
{
new PlaylistItem { ID = 1, BeatmapID = 1001, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 2, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
new PlaylistItem { ID = 3, BeatmapID = 1002, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) },
new PlaylistItem(new APIBeatmap { OnlineID = 1001 }) { ID = 1, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 55, 0, TimeSpan.Zero) },
new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 2, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 53, 0, TimeSpan.Zero) },
new PlaylistItem(new APIBeatmap { OnlineID = 1002 }) { ID = 3, Expired = true, PlayedAt = new DateTimeOffset(2021, 12, 21, 7, 57, 0, TimeSpan.Zero) },
};
Assert.Multiple(() =>

View File

@ -0,0 +1,132 @@
// 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.Game.Utils;
namespace osu.Game.Tests.Utils
{
[TestFixture]
public class NamingUtilsTest
{
[Test]
public void TestEmptySet()
{
string nextBestName = NamingUtils.GetNextBestName(Enumerable.Empty<string>(), "New Difficulty");
Assert.AreEqual("New Difficulty", nextBestName);
}
[Test]
public void TestNotTaken()
{
string[] existingNames =
{
"Something",
"Entirely",
"Different"
};
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
Assert.AreEqual("New Difficulty", nextBestName);
}
[Test]
public void TestNotTakenButClose()
{
string[] existingNames =
{
"New Difficulty(1)",
"New Difficulty (abcd)",
"New Difficulty but not really"
};
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
Assert.AreEqual("New Difficulty", nextBestName);
}
[Test]
public void TestAlreadyTaken()
{
string[] existingNames =
{
"New Difficulty"
};
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
Assert.AreEqual("New Difficulty (1)", nextBestName);
}
[Test]
public void TestAlreadyTakenWithDifferentCase()
{
string[] existingNames =
{
"new difficulty"
};
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
Assert.AreEqual("New Difficulty (1)", nextBestName);
}
[Test]
public void TestAlreadyTakenWithBrackets()
{
string[] existingNames =
{
"new difficulty (copy)"
};
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty (copy)");
Assert.AreEqual("New Difficulty (copy) (1)", nextBestName);
}
[Test]
public void TestMultipleAlreadyTaken()
{
string[] existingNames =
{
"New Difficulty",
"New difficulty (1)",
"new Difficulty (2)",
"New DIFFICULTY (3)"
};
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
Assert.AreEqual("New Difficulty (4)", nextBestName);
}
[Test]
public void TestEvenMoreAlreadyTaken()
{
string[] existingNames = Enumerable.Range(1, 30).Select(i => $"New Difficulty ({i})").Append("New Difficulty").ToArray();
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
Assert.AreEqual("New Difficulty (31)", nextBestName);
}
[Test]
public void TestMultipleAlreadyTakenWithGaps()
{
string[] existingNames =
{
"New Difficulty",
"New Difficulty (1)",
"New Difficulty (4)",
"New Difficulty (9)"
};
string nextBestName = NamingUtils.GetNextBestName(existingNames, "New Difficulty");
Assert.AreEqual("New Difficulty (2)", nextBestName);
}
}
}

View File

@ -269,11 +269,12 @@ namespace osu.Game.Tests.Visual.Editing
}
[Test]
public void TestCreateNewBeatmapFailsWithBlankNamedDifficulties()
public void TestCreateMultipleNewDifficultiesSucceeds()
{
Guid setId = Guid.Empty;
AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
AddStep("set difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = "New Difficulty");
AddStep("save beatmap", () => Editor.Save());
AddAssert("new beatmap persisted", () =>
{
@ -282,15 +283,24 @@ namespace osu.Game.Tests.Visual.Editing
});
AddStep("try to create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
AddAssert("beatmap set unchanged", () =>
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog.PerformOkAction());
AddUntilStep("wait for created", () =>
{
string difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
return difficultyName != null && difficultyName != "New Difficulty";
});
AddAssert("new difficulty has correct name", () => EditorBeatmap.BeatmapInfo.DifficultyName == "New Difficulty (1)");
AddAssert("new difficulty persisted", () =>
{
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
return set != null && set.PerformRead(s => s.Beatmaps.Count == 1 && s.Files.Count == 1);
return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
});
}
[Test]
public void TestCreateNewBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset)
public void TestSavingBeatmapFailsWithSameNamedDifficulties([Values] bool sameRuleset)
{
Guid setId = Guid.Empty;
const string duplicate_difficulty_name = "duplicate";

View File

@ -71,9 +71,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = { Value = Mode },
Playlist =
{
new PlaylistItem
new PlaylistItem(InitialBeatmap)
{
Beatmap = { Value = InitialBeatmap },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}

View File

@ -53,19 +53,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
Type = { Value = MatchType.HeadToHead },
Playlist =
{
new PlaylistItem
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
Beatmap =
BeatmapInfo =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 2.5
}
}.BeatmapInfo,
StarRating = 2.5
}
}
}.BeatmapInfo)
}
}),
createLoungeRoom(new Room
@ -76,26 +70,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
Type = { Value = MatchType.HeadToHead },
Playlist =
{
new PlaylistItem
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
Beatmap =
BeatmapInfo =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
StarRating = 2.5,
Metadata =
{
BeatmapInfo =
{
StarRating = 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,
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
@ -105,32 +93,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
EndDate = { Value = DateTimeOffset.Now.AddDays(1) },
Playlist =
{
new PlaylistItem
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
Beatmap =
BeatmapInfo =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 2.5
}
}.BeatmapInfo,
StarRating = 2.5
}
},
new PlaylistItem
}.BeatmapInfo),
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo)
{
Beatmap =
BeatmapInfo =
{
Value = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
BeatmapInfo =
{
StarRating = 4.5
}
}.BeatmapInfo,
StarRating = 4.5
}
}
}.BeatmapInfo)
}
}),
createLoungeRoom(new Room

View File

@ -168,7 +168,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertDownloadButtonVisible(false);
void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}",
() => playlist.ChildrenOfType<BeatmapDownloadButton>().Single().Alpha == (visible ? 1 : 0));
() => playlist.ChildrenOfType<BeatmapDownloadButton>().SingleOrDefault()?.Alpha == (visible ? 1 : 0));
}
[Test]
@ -209,10 +209,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
Size = new Vector2(500, 300),
Items =
{
new PlaylistItem
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
ID = 0,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Expired = true,
RequiredMods = new[]
@ -222,10 +221,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
new APIMod(new OsuModAutoplay())
}
},
new PlaylistItem
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
ID = 1,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
@ -262,7 +260,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
private void moveToItem(int index, Vector2? offset = null)
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DrawableRoomPlaylistItem>().ElementAt(index), offset));
private void moveToDragger(int index, Vector2? offset = null) => AddStep($"move mouse to dragger {index}", () =>
{
@ -293,25 +291,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
for (int i = 0; i < 20; i++)
{
playlist.Items.Add(new PlaylistItem
playlist.Items.Add(new PlaylistItem(i % 2 == 1
? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
: new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = "Artist",
Author = new RealmUser { Username = "Creator name here" },
Title = "Long title used to check background colour",
},
BeatmapSet = new BeatmapSetInfo()
})
{
ID = i,
OwnerID = 2,
Beatmap =
{
Value = i % 2 == 1
? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
: new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = "Artist",
Author = new RealmUser { Username = "Creator name here" },
Title = "Long title used to check background colour",
},
BeatmapSet = new BeatmapSetInfo()
}
},
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
@ -341,11 +335,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
foreach (var b in beatmaps())
{
playlist.Items.Add(new PlaylistItem
playlist.Items.Add(new PlaylistItem(b)
{
ID = index++,
OwnerID = 2,
Beatmap = { Value = b },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{

View File

@ -58,12 +58,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
RunGameplay();
IBeatmapInfo firstBeatmap = null;
AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.APIRoom?.Playlist[0].Beatmap.Value);
AddStep("get first playlist item beatmap", () => firstBeatmap = MultiplayerClient.APIRoom?.Playlist[0].Beatmap);
selectNewItem(() => OtherBeatmap);
AddAssert("first playlist item hasn't changed", () => MultiplayerClient.APIRoom?.Playlist[0].Beatmap.Value == firstBeatmap);
AddAssert("second playlist item changed", () => MultiplayerClient.APIRoom?.Playlist[1].Beatmap.Value != firstBeatmap);
AddAssert("first playlist item hasn't changed", () => MultiplayerClient.APIRoom?.Playlist[0].Beatmap == firstBeatmap);
AddAssert("second playlist item changed", () => MultiplayerClient.APIRoom?.Playlist[1].Beatmap != firstBeatmap);
}
[Test]
@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.BeatmapID == otherBeatmap.OnlineID);
AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.Beatmap.OnlineID == otherBeatmap.OnlineID);
}
private void addItem(Func<BeatmapInfo> beatmap)

View File

@ -32,10 +32,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void createNewItem()
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
ID = SelectedRoom.Value.Playlist.Count,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{

View File

@ -93,9 +93,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -229,9 +228,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -251,9 +249,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -281,9 +278,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -312,9 +308,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Password = { Value = "password" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -334,9 +329,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Password = { Value = "password" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -367,9 +361,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Password = { Value = "password" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -387,9 +380,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -409,9 +401,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -448,9 +439,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -487,9 +477,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -526,9 +515,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -560,9 +548,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -600,9 +587,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -620,9 +606,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new OsuModHidden()) }
}
@ -660,10 +645,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
});
@ -691,10 +675,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
}, API.LocalUser.Value);
@ -708,11 +691,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("change server-side settings", () =>
{
roomManager.ServerSideRooms[0].Name.Value = "New name";
roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem
roomManager.ServerSideRooms[0].Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
ID = 2,
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
});
});
@ -737,10 +719,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
});
@ -773,10 +754,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
});
@ -812,21 +792,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
});
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
{
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID
})).WaitSafely());
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
})).WaitSafely());
AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2);
@ -843,10 +822,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
QueueMode = { Value = QueueMode.AllPlayers },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
});
@ -854,10 +832,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
enterGameplay();
AddStep("join other user", () => multiplayerClient.AddUser(new APIUser { Id = 1234 }));
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(new PlaylistItem
{
BeatmapID = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo.OnlineID
})).WaitSafely());
AddStep("add item as other user", () => multiplayerClient.AddUserPlaylistItem(1234, new MultiplayerPlaylistItem(
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
})).WaitSafely());
AddUntilStep("item arrived in playlist", () => multiplayerClient.Room?.Playlist.Count == 2);
@ -876,9 +855,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}

View File

@ -73,9 +73,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
});
@ -90,9 +89,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(new TaikoRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new TaikoModSwap()) }
});
@ -113,9 +111,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
});
@ -128,9 +125,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("set playlist", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
});
@ -159,9 +155,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
AddStep("add playlist item with allowed mod", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
AllowedMods = new[] { new APIMod(new OsuModDoubleTime()) }
});

View File

@ -28,9 +28,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("initialise gameplay", () =>
{
Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.APIRoom, new PlaylistItem
Stack.Push(player = new MultiplayerPlayer(MultiplayerClient.APIRoom, new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
}, MultiplayerClient.Room?.Users.ToArray()));
});

View File

@ -143,14 +143,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "test name" },
Playlist =
{
new PlaylistItem
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
RulesetID = Ruleset.Value.OnlineID,
RulesetID = Ruleset.Value.OnlineID
},
new PlaylistItem
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
RulesetID = Ruleset.Value.OnlineID,
Expired = true
}
@ -167,10 +165,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
/// <summary>
/// Adds a step to create a new playlist item.
/// </summary>
private void addItemStep(bool expired = false) => AddStep("add item", () => MultiplayerClient.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem
private void addItemStep(bool expired = false) => AddStep("add item", () => MultiplayerClient.AddPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap)
{
Beatmap = { Value = importedBeatmap },
BeatmapID = importedBeatmap.OnlineID,
Expired = expired,
PlayedAt = DateTimeOffset.Now
})));

View File

@ -120,11 +120,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("add playlist item", () =>
{
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem
{
Beatmap = { Value = importedBeatmap },
BeatmapID = importedBeatmap.OnlineID,
});
MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem(importedBeatmap));
MultiplayerClient.AddUserPlaylistItem(userId(), item).WaitSafely();

View File

@ -54,9 +54,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem
selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID
};

View File

@ -22,12 +22,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
PlaylistItem playlistItem = new PlaylistItem
{
BeatmapID = beatmapInfo.OnlineID,
};
Stack.Push(screen = new MultiplayerResultsScreen(score, 1, playlistItem));
Stack.Push(screen = new MultiplayerResultsScreen(score, 1, new PlaylistItem(beatmapInfo)));
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);

View File

@ -55,9 +55,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First());
selectedItem.Value = new PlaylistItem
selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID,
};

View File

@ -26,18 +26,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo;
var score = TestResources.CreateTestScoreInfo(beatmapInfo);
PlaylistItem playlistItem = new PlaylistItem
{
BeatmapID = beatmapInfo.OnlineID,
};
SortedDictionary<int, BindableInt> teamScores = new SortedDictionary<int, BindableInt>
{
{ 0, new BindableInt(team1Score) },
{ 1, new BindableInt(team2Score) }
};
Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, playlistItem, teamScores));
Stack.Push(screen = new MultiplayerTeamResultsScreen(score, 1, new PlaylistItem(beatmapInfo), teamScores));
});
AddUntilStep("wait for loaded", () => screen.IsLoaded);

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Models;
using osu.Game.Online.API;
@ -107,18 +106,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]);
}
[Test]
public void TestChangeBeatmapAndRemove()
{
createPlaylist();
AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30);
moveToDeleteButton(0);
AddStep("click delete button", () => InputManager.Click(MouseButton.Left));
}
private void moveToItem(int index, Vector2? offset = null)
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DifficultyIcon>().ElementAt(index), offset));
=> AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType<DrawableRoomPlaylistItem>().ElementAt(index), offset));
private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () =>
{
@ -139,25 +128,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
for (int i = 0; i < 20; i++)
{
playlist.Items.Add(new PlaylistItem
playlist.Items.Add(new PlaylistItem(i % 2 == 1
? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
: new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = "Artist",
Author = new RealmUser { Username = "Creator name here" },
Title = "Long title used to check background colour",
},
BeatmapSet = new BeatmapSetInfo()
})
{
ID = i,
OwnerID = 2,
Beatmap =
{
Value = i % 2 == 1
? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo
: new BeatmapInfo
{
Metadata = new BeatmapMetadata
{
Artist = "Artist",
Author = new RealmUser { Username = "Creator name here" },
Title = "Long title used to check background colour",
},
BeatmapSet = new BeatmapSetInfo()
}
},
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{

View File

@ -31,8 +31,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
SelectedRoom.Value.Playlist.AddRange(new[]
{
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = min } } },
new PlaylistItem { Beatmap = { Value = new BeatmapInfo { StarRating = max } } },
new PlaylistItem(new BeatmapInfo { StarRating = min }),
new PlaylistItem(new BeatmapInfo { StarRating = max }),
});
});
}

View File

@ -67,9 +67,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Type = { Value = MatchType.TeamVersus },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -88,9 +87,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Type = { Value = MatchType.TeamVersus },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}
}
@ -126,10 +124,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
Type = { Value = MatchType.HeadToHead },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
});
@ -152,10 +149,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "Test Room" },
Playlist =
{
new PlaylistItem
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
{
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
}
}
});

View File

@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("set name", () => SelectedRoom.Value.Name.Value = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value);
AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } }));
AddStep("set beatmap", () => SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo)));
AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value);
AddStep("clear name", () => SelectedRoom.Value.Name.Value = "");
@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo));
RoomManager.CreateRequested = r =>
{
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Playlists
var beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo;
SelectedRoom.Value.Name.Value = "Test Room";
SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = beatmap } });
SelectedRoom.Value.Playlist.Add(new PlaylistItem(beatmap));
errorMessage = $"{not_found_prefix} {beatmap.OnlineID}";
@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Playlists
AddStep("setup", () =>
{
SelectedRoom.Value.Name.Value = "Test Room";
SelectedRoom.Value.Playlist.Add(new PlaylistItem { Beatmap = { Value = CreateBeatmap(Ruleset.Value).BeatmapInfo } });
SelectedRoom.Value.Playlist.Add(new PlaylistItem(CreateBeatmap(Ruleset.Value).BeatmapInfo));
RoomManager.CreateRequested = _ => failText;
});

View File

@ -165,9 +165,8 @@ namespace osu.Game.Tests.Visual.Playlists
{
AddStep("load results", () =>
{
LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem
LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
}));
});

View File

@ -64,9 +64,8 @@ namespace osu.Game.Tests.Visual.Playlists
room.Host.Value = API.LocalUser.Value;
room.RecentParticipants.Add(room.Host.Value);
room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
room.Playlist.Add(new PlaylistItem
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{
Beatmap = { Value = importedBeatmap.Beatmaps.First() },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
});
@ -89,9 +88,8 @@ namespace osu.Game.Tests.Visual.Playlists
room.Host.Value = API.LocalUser.Value;
room.RecentParticipants.Add(room.Host.Value);
room.EndDate.Value = DateTimeOffset.Now.AddMinutes(5);
room.Playlist.Add(new PlaylistItem
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{
Beatmap = { Value = importedBeatmap.Beatmaps.First() },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
});
@ -106,9 +104,8 @@ namespace osu.Game.Tests.Visual.Playlists
{
room.Name.Value = "my awesome room";
room.Host.Value = API.LocalUser.Value;
room.Playlist.Add(new PlaylistItem
room.Playlist.Add(new PlaylistItem(importedBeatmap.Beatmaps.First())
{
Beatmap = { Value = importedBeatmap.Beatmaps.First() },
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
});
@ -158,21 +155,17 @@ namespace osu.Game.Tests.Visual.Playlists
{
room.Name.Value = "my awesome room";
room.Host.Value = API.LocalUser.Value;
room.Playlist.Add(new PlaylistItem
room.Playlist.Add(new PlaylistItem(new BeatmapInfo
{
Beatmap =
MD5Hash = realHash,
OnlineID = realOnlineId,
Metadata = new BeatmapMetadata(),
BeatmapSet = new BeatmapSetInfo
{
Value = new BeatmapInfo
{
MD5Hash = realHash,
OnlineID = realOnlineId,
Metadata = new BeatmapMetadata(),
BeatmapSet = new BeatmapSetInfo
{
OnlineID = realOnlineSetId,
}
}
},
OnlineID = realOnlineSetId,
}
})
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
});
});

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Screens;
using osu.Game.Database;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens;
@ -46,6 +47,9 @@ namespace osu.Game.Tests.Visual
[Cached]
private readonly BeatmapLookupCache beatmapLookupCache = new BeatmapLookupCache();
[Resolved]
private BeatmapManager beatmapManager { get; set; }
private readonly OsuScreenStack screenStack;
private readonly TestMultiplayer multiplayerScreen;
@ -69,9 +73,9 @@ namespace osu.Game.Tests.Visual
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api, OsuGameBase game)
private void load(IAPIProvider api)
{
((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, game);
((DummyAPIAccess)api).HandleRequest = request => multiplayerScreen.RequestsHandler.HandleRequest(request, api.LocalUser.Value, beatmapManager);
}
public override bool OnBackButton() => (screenStack.CurrentScreen as OsuScreen)?.OnBackButton() ?? base.OnBackButton();

View File

@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Select;
@ -12,11 +14,11 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneFooterButtonMods : OsuTestScene
{
private readonly FooterButtonMods footerButtonMods;
private readonly TestFooterButtonMods footerButtonMods;
public TestSceneFooterButtonMods()
{
Add(footerButtonMods = new FooterButtonMods());
Add(footerButtonMods = new TestFooterButtonMods());
}
[Test]
@ -24,15 +26,19 @@ namespace osu.Game.Tests.Visual.UserInterface
{
var hiddenMod = new Mod[] { new OsuModHidden() };
AddStep(@"Add Hidden", () => changeMods(hiddenMod));
AddAssert(@"Check Hidden multiplier", () => assertModsMultiplier(hiddenMod));
var hardRockMod = new Mod[] { new OsuModHardRock() };
AddStep(@"Add HardRock", () => changeMods(hardRockMod));
AddAssert(@"Check HardRock multiplier", () => assertModsMultiplier(hardRockMod));
var doubleTimeMod = new Mod[] { new OsuModDoubleTime() };
AddStep(@"Add DoubleTime", () => changeMods(doubleTimeMod));
AddAssert(@"Check DoubleTime multiplier", () => assertModsMultiplier(doubleTimeMod));
var multipleIncrementMods = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModHardRock() };
AddStep(@"Add multiple Mods", () => changeMods(multipleIncrementMods));
AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleIncrementMods));
}
[Test]
@ -40,12 +46,15 @@ namespace osu.Game.Tests.Visual.UserInterface
{
var easyMod = new Mod[] { new OsuModEasy() };
AddStep(@"Add Easy", () => changeMods(easyMod));
AddAssert(@"Check Easy multiplier", () => assertModsMultiplier(easyMod));
var noFailMod = new Mod[] { new OsuModNoFail() };
AddStep(@"Add NoFail", () => changeMods(noFailMod));
AddAssert(@"Check NoFail multiplier", () => assertModsMultiplier(noFailMod));
var multipleDecrementMods = new Mod[] { new OsuModEasy(), new OsuModNoFail() };
AddStep(@"Add Multiple Mods", () => changeMods(multipleDecrementMods));
AddAssert(@"Check multiple mod multiplier", () => assertModsMultiplier(multipleDecrementMods));
}
[Test]
@ -54,11 +63,25 @@ namespace osu.Game.Tests.Visual.UserInterface
var multipleMods = new Mod[] { new OsuModDoubleTime(), new OsuModFlashlight() };
AddStep(@"Add mods", () => changeMods(multipleMods));
AddStep(@"Clear selected mod", () => changeMods(Array.Empty<Mod>()));
AddAssert(@"Check empty multiplier", () => assertModsMultiplier(Array.Empty<Mod>()));
}
private void changeMods(IReadOnlyList<Mod> mods)
{
footerButtonMods.Current.Value = mods;
}
private bool assertModsMultiplier(IEnumerable<Mod> mods)
{
double multiplier = mods.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier);
string expectedValue = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
return expectedValue == footerButtonMods.MultiplierText.Current.Value;
}
private class TestFooterButtonMods : FooterButtonMods
{
public new OsuSpriteText MultiplierText => base.MultiplierText;
}
}
}

View File

@ -22,6 +22,7 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Skinning;
using osu.Game.Stores;
using osu.Game.Utils;
#nullable enable
@ -123,7 +124,10 @@ namespace osu.Game.Beatmaps
{
var playableBeatmap = referenceWorkingBeatmap.GetPlayableBeatmap(rulesetInfo);
var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone());
var newBeatmapInfo = new BeatmapInfo(rulesetInfo, new BeatmapDifficulty(), playableBeatmap.Metadata.DeepClone())
{
DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty")
};
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
foreach (var timingPoint in playableBeatmap.ControlPointInfo.TimingPoints)
newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
@ -150,8 +154,10 @@ namespace osu.Game.Beatmaps
newBeatmap.BeatmapInfo = newBeatmapInfo = referenceWorkingBeatmap.BeatmapInfo.Clone();
// assign a new ID to the clone.
newBeatmapInfo.ID = Guid.NewGuid();
// add "(copy)" suffix to difficulty name to avoid clashes on save.
newBeatmapInfo.DifficultyName += " (copy)";
// add "(copy)" suffix to difficulty name, and additionally ensure that it doesn't conflict with any other potentially pre-existing copies.
newBeatmapInfo.DifficultyName = NamingUtils.GetNextBestName(
targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName),
$"{newBeatmapInfo.DifficultyName} (copy)");
// clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps.
newBeatmapInfo.Hash = string.Empty;
// clear online properties.

View File

@ -264,32 +264,58 @@ namespace osu.Game.Graphics
public readonly Color4 GrayE = Color4Extensions.FromHex(@"eee");
public readonly Color4 GrayF = Color4Extensions.FromHex(@"fff");
/// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Pink"/>'s <see cref="OverlayColourProvider.Colour3"/>.
/// </summary>
public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378");
#region "Basic" colour theme
/// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Blue"/>'s <see cref="OverlayColourProvider.Colour3"/>.
/// </summary>
// Reference: https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Asset%2FColours?node-id=1838%3A3
// Note that the colours in this region are also defined in `OverlayColourProvider` as `Colour{0,1,2,3,4}`.
// The difference as to which should be used where comes down to context.
// If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`.
// If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants.
public readonly Color4 Pink0 = Color4Extensions.FromHex(@"ff99c7");
public readonly Color4 Pink1 = Color4Extensions.FromHex(@"ff66ab");
public readonly Color4 Pink2 = Color4Extensions.FromHex(@"eb4791");
public readonly Color4 Pink3 = Color4Extensions.FromHex(@"cc3378");
public readonly Color4 Pink4 = Color4Extensions.FromHex(@"6b2e49");
public readonly Color4 Purple0 = Color4Extensions.FromHex(@"b299ff");
public readonly Color4 Purple1 = Color4Extensions.FromHex(@"8c66ff");
public readonly Color4 Purple2 = Color4Extensions.FromHex(@"7047eb");
public readonly Color4 Purple3 = Color4Extensions.FromHex(@"5933cc");
public readonly Color4 Purple4 = Color4Extensions.FromHex(@"3d2e6b");
public readonly Color4 Blue0 = Color4Extensions.FromHex(@"99ddff");
public readonly Color4 Blue1 = Color4Extensions.FromHex(@"66ccff");
public readonly Color4 Blue2 = Color4Extensions.FromHex(@"47b4eb");
public readonly Color4 Blue3 = Color4Extensions.FromHex(@"3399cc");
public readonly Color4 Blue4 = Color4Extensions.FromHex(@"2e576b");
public readonly Color4 Green0 = Color4Extensions.FromHex(@"99ffa2");
public readonly Color4 Green1 = Color4Extensions.FromHex(@"66ff73");
public readonly Color4 Green2 = Color4Extensions.FromHex(@"47eb55");
public readonly Color4 Green3 = Color4Extensions.FromHex(@"33cc40");
public readonly Color4 Green4 = Color4Extensions.FromHex(@"2e6b33");
public readonly Color4 Lime0 = Color4Extensions.FromHex(@"ccff99");
/// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour1"/>.
/// </summary>
public readonly Color4 Lime1 = Color4Extensions.FromHex(@"b2ff66");
/// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Lime"/>'s <see cref="OverlayColourProvider.Colour3"/>.
/// </summary>
public readonly Color4 Lime2 = Color4Extensions.FromHex(@"99eb47");
public readonly Color4 Lime3 = Color4Extensions.FromHex(@"7fcc33");
public readonly Color4 Lime4 = Color4Extensions.FromHex(@"4c6b2e");
/// <summary>
/// Equivalent to <see cref="OverlayColourProvider.Orange"/>'s <see cref="OverlayColourProvider.Colour1"/>.
/// </summary>
public readonly Color4 Orange0 = Color4Extensions.FromHex(@"ffe699");
public readonly Color4 Orange1 = Color4Extensions.FromHex(@"ffd966");
public readonly Color4 Orange2 = Color4Extensions.FromHex(@"ebc247");
public readonly Color4 Orange3 = Color4Extensions.FromHex(@"cca633");
public readonly Color4 Orange4 = Color4Extensions.FromHex(@"6b5c2e");
public readonly Color4 Red0 = Color4Extensions.FromHex(@"ff9b9b");
public readonly Color4 Red1 = Color4Extensions.FromHex(@"ff6666");
public readonly Color4 Red2 = Color4Extensions.FromHex(@"eb4747");
public readonly Color4 Red3 = Color4Extensions.FromHex(@"cc3333");
public readonly Color4 Red4 = Color4Extensions.FromHex(@"6b2e2e");
#endregion
// Content Background
public readonly Color4 B5 = Color4Extensions.FromHex(@"222a28");

View File

@ -112,6 +112,8 @@ namespace osu.Game.Graphics
if (Interlocked.Decrement(ref screenShotTasks) == 0 && cursorVisibility.Value == false)
cursorVisibility.Value = true;
host.GetClipboard()?.SetImage(image);
string filename = getFilename();
if (filename == null) return;

View File

@ -2,12 +2,13 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
namespace osu.Game.Online.API.Requests
{
public class GetBeatmapsRequest : APIRequest<GetBeatmapsResponse>
{
private readonly int[] beatmapIds;
public readonly IReadOnlyList<int> BeatmapIds;
private const int max_ids_per_request = 50;
@ -16,9 +17,9 @@ namespace osu.Game.Online.API.Requests
if (beatmapIds.Length > max_ids_per_request)
throw new ArgumentException($"{nameof(GetBeatmapsRequest)} calls only support up to {max_ids_per_request} IDs at once");
this.beatmapIds = beatmapIds;
BeatmapIds = beatmapIds;
}
protected override string Target => "beatmaps/?ids[]=" + string.Join("&ids[]=", beatmapIds);
protected override string Target => "beatmaps/?ids[]=" + string.Join("&ids[]=", BeatmapIds);
}
}

View File

@ -112,7 +112,27 @@ namespace osu.Game.Online.API.Requests.Responses
public int OnlineID { get; set; } = -1;
public string Name => $@"{nameof(APIRuleset)} (ID: {OnlineID})";
public string ShortName => nameof(APIRuleset);
public string ShortName
{
get
{
// TODO: this should really not exist.
switch (OnlineID)
{
case 0: return "osu";
case 1: return "taiko";
case 2: return "fruits";
case 3: return "mania";
default: throw new ArgumentOutOfRangeException();
}
}
}
public string InstantiationInfo => string.Empty;
public Ruleset CreateInstance() => throw new NotImplementedException();

View File

@ -727,10 +727,9 @@ namespace osu.Game.Online.Multiplayer
RoomUpdated?.Invoke();
}
private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem
private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) => new PlaylistItem(new APIBeatmap { OnlineID = item.BeatmapID })
{
ID = item.ID,
BeatmapID = item.BeatmapID,
OwnerID = item.OwnerID,
RulesetID = item.RulesetID,
Expired = item.Expired,
@ -740,14 +739,6 @@ namespace osu.Game.Online.Multiplayer
AllowedMods = item.AllowedMods.ToArray()
};
/// <summary>
/// Retrieves a <see cref="APIBeatmap"/> from an online source.
/// </summary>
/// <param name="beatmapId">The beatmap ID.</param>
/// <param name="cancellationToken">A token to cancel the request.</param>
/// <returns>The <see cref="APIBeatmap"/> retrieval task.</returns>
public abstract Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default);
/// <summary>
/// For the provided user ID, update whether the user is included in <see cref="CurrentMatchPlayingUserIds"/>.
/// </summary>

View File

@ -10,9 +10,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
namespace osu.Game.Online.Multiplayer
@ -30,9 +28,6 @@ namespace osu.Game.Online.Multiplayer
private HubConnection? connection => connector?.CurrentConnection;
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
public OnlineMultiplayerClient(EndpointConfiguration endpoints)
{
endpoint = endpoints.MultiplayerEndpointUrl;
@ -215,11 +210,6 @@ namespace osu.Game.Online.Multiplayer
return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId);
}
public override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
{
return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);

View File

@ -63,8 +63,8 @@ namespace osu.Game.Online.Rooms
{
ID = item.ID;
OwnerID = item.OwnerID;
BeatmapID = item.BeatmapID;
BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty;
BeatmapID = item.Beatmap.OnlineID;
BeatmapChecksum = item.Beatmap.MD5Hash;
RulesetID = item.RulesetID;
RequiredMods = item.RequiredMods.ToArray();
AllowedMods = item.AllowedMods.ToArray();

View File

@ -4,14 +4,17 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Online.API.Requests.Responses;
using Realms;
namespace osu.Game.Online.Rooms
@ -32,6 +35,9 @@ namespace osu.Game.Online.Rooms
[Resolved]
private RealmAccess realm { get; set; } = null!;
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; } = null!;
/// <summary>
/// The availability state of the currently selected playlist item.
/// </summary>
@ -40,10 +46,9 @@ namespace osu.Game.Online.Rooms
private readonly Bindable<BeatmapAvailability> availability = new Bindable<BeatmapAvailability>(BeatmapAvailability.NotDownloaded());
private ScheduledDelegate progressUpdate;
private BeatmapDownloadTracker downloadTracker;
private IDisposable realmSubscription;
private APIBeatmap selectedBeatmap;
protected override void LoadComplete()
{
@ -57,40 +62,55 @@ namespace osu.Game.Online.Rooms
return;
downloadTracker?.RemoveAndDisposeImmediately();
selectedBeatmap = null;
Debug.Assert(item.NewValue.Beatmap.Value.BeatmapSet != null);
downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet);
AddInternal(downloadTracker);
downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability), true);
downloadTracker.Progress.BindValueChanged(_ =>
beatmapLookupCache.GetBeatmapAsync(item.NewValue.Beatmap.OnlineID).ContinueWith(task => Schedule(() =>
{
if (downloadTracker.State.Value != DownloadState.Downloading)
return;
var beatmap = task.GetResultSafely();
// incoming progress changes are going to be at a very high rate.
// 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);
}, true);
// handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow).
realmSubscription?.Dispose();
realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) =>
{
if (changes == null)
return;
Scheduler.AddOnce(updateAvailability);
});
if (SelectedItem.Value?.Beatmap.OnlineID == beatmap.OnlineID)
{
selectedBeatmap = beatmap;
beginTracking();
}
}), TaskContinuationOptions.OnlyOnRanToCompletion);
}, true);
}
private void beginTracking()
{
Debug.Assert(selectedBeatmap.BeatmapSet != null);
downloadTracker = new BeatmapDownloadTracker(selectedBeatmap.BeatmapSet);
AddInternal(downloadTracker);
downloadTracker.State.BindValueChanged(_ => Scheduler.AddOnce(updateAvailability), true);
downloadTracker.Progress.BindValueChanged(_ =>
{
if (downloadTracker.State.Value != DownloadState.Downloading)
return;
// incoming progress changes are going to be at a very high rate.
// 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);
}, true);
// handles changes to hash that didn't occur from the import process (ie. a user editing the beatmap in the editor, somehow).
realmSubscription?.Dispose();
realmSubscription = realm.RegisterForNotifications(r => filteredBeatmaps(), (items, changes, ___) =>
{
if (changes == null)
return;
Scheduler.AddOnce(updateAvailability);
});
}
private void updateAvailability()
{
if (downloadTracker == null || SelectedItem.Value == null)
if (downloadTracker == null || selectedBeatmap == null)
return;
switch (downloadTracker.State.Value)
@ -108,12 +128,12 @@ namespace osu.Game.Online.Rooms
break;
case DownloadState.LocallyAvailable:
bool hashMatches = filteredBeatmaps().Any();
bool available = filteredBeatmaps().Any();
availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded();
availability.Value = available ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded();
// only display a message to the user if a download seems to have just completed.
if (!hashMatches && downloadTracker.Progress.Value == 1)
if (!available && downloadTracker.Progress.Value == 1)
Logger.Log("The imported beatmap set does not match the online version.", LoggingTarget.Runtime, LogLevel.Important);
break;
@ -125,8 +145,8 @@ namespace osu.Game.Online.Rooms
private IQueryable<BeatmapInfo> filteredBeatmaps()
{
int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID;
string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash;
int onlineId = selectedBeatmap.OnlineID;
string checksum = selectedBeatmap.MD5Hash;
return realm.Realm
.All<BeatmapInfo>()

View File

@ -41,6 +41,6 @@ namespace osu.Game.Online.Rooms
}
public static string GetTotalDuration(this BindableList<PlaylistItem> playlist) =>
playlist.Select(p => p.Beatmap.Value.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2);
playlist.Select(p => p.Beatmap.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2);
}
}

View File

@ -1,6 +1,8 @@
// 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 System;
using System.Linq;
using JetBrains.Annotations;
@ -12,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.Rooms
{
[JsonObject(MemberSerialization.OptIn)]
public class PlaylistItem : IEquatable<PlaylistItem>
{
[JsonProperty("id")]
@ -20,9 +23,6 @@ namespace osu.Game.Online.Rooms
[JsonProperty("owner_id")]
public int OwnerID { get; set; }
[JsonProperty("beatmap_id")]
public int BeatmapID { get; set; }
[JsonProperty("ruleset_id")]
public int RulesetID { get; set; }
@ -38,35 +38,50 @@ namespace osu.Game.Online.Rooms
[JsonProperty("played_at")]
public DateTimeOffset? PlayedAt { get; set; }
[JsonIgnore]
public IBindable<bool> Valid => valid;
private readonly Bindable<bool> valid = new BindableBool(true);
[JsonIgnore]
public readonly Bindable<IBeatmapInfo> Beatmap = new Bindable<IBeatmapInfo>();
[JsonProperty("beatmap")]
private APIBeatmap apiBeatmap { get; set; }
[JsonProperty("allowed_mods")]
public APIMod[] AllowedMods { get; set; } = Array.Empty<APIMod>();
[JsonProperty("required_mods")]
public APIMod[] RequiredMods { get; set; } = Array.Empty<APIMod>();
public PlaylistItem()
/// <summary>
/// Used for deserialising from the API.
/// </summary>
[JsonProperty("beatmap")]
private APIBeatmap apiBeatmap
{
Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1);
// This getter is required/used internally by JSON.NET during deserialisation to do default-value comparisons. It is never used during serialisation (see: ShouldSerializeapiBeatmap()).
// It will always return a null value on deserialisation, which JSON.NET will handle gracefully.
get => (APIBeatmap)Beatmap;
set => Beatmap = value;
}
/// <summary>
/// Used for serialising to the API.
/// </summary>
[JsonProperty("beatmap_id")]
private int onlineBeatmapId => Beatmap.OnlineID;
[JsonIgnore]
public IBeatmapInfo Beatmap { get; set; } = null!;
[JsonIgnore]
public IBindable<bool> Valid => valid;
private readonly Bindable<bool> valid = new BindableBool(true);
[JsonConstructor]
private PlaylistItem()
{
}
public PlaylistItem(IBeatmapInfo beatmap)
{
Beatmap = beatmap;
}
public void MarkInvalid() => valid.Value = false;
public void MapObjects()
{
Beatmap.Value ??= apiBeatmap;
}
#region Newtonsoft.Json implicit ShouldSerialize() methods
// The properties in this region are used implicitly by Newtonsoft.Json to not serialise certain fields in some cases.
@ -82,9 +97,22 @@ namespace osu.Game.Online.Rooms
#endregion
public bool Equals(PlaylistItem other)
public PlaylistItem With(IBeatmapInfo beatmap) => new PlaylistItem(beatmap)
{
ID = ID,
OwnerID = OwnerID,
RulesetID = RulesetID,
Expired = Expired,
PlaylistOrder = PlaylistOrder,
PlayedAt = PlayedAt,
AllowedMods = AllowedMods,
RequiredMods = RequiredMods,
valid = { Value = Valid.Value },
};
public bool Equals(PlaylistItem? other)
=> ID == other?.ID
&& BeatmapID == other.BeatmapID
&& Beatmap.OnlineID == other.Beatmap.OnlineID
&& RulesetID == other.RulesetID
&& Expired == other.Expired
&& AllowedMods.SequenceEqual(other.AllowedMods)

View File

@ -1,6 +1,8 @@
// 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.Game.Graphics;
using osu.Game.Resources.Localisation.Web;
using osuTK.Graphics;
@ -33,7 +35,10 @@ namespace osu.Game.Overlays.BeatmapListing
{
}
protected override Color4 GetStateColour() => OverlayColourProvider.Orange.Colour1;
[Resolved]
private OsuColour colours { get; set; }
protected override Color4 GetStateColour() => colours.Orange1;
}
}
}

View File

@ -44,7 +44,14 @@ namespace osu.Game.Overlays.BeatmapListing
});
Enabled.Value = true;
}
protected override void LoadComplete()
{
base.LoadComplete();
updateState();
FinishTransforms(true);
}
protected override bool OnHover(HoverEvent e)

View File

@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet
Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f },
Text = BeatmapsetsStrings.NsfwBadgeLabel.ToUpper(),
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
Colour = OverlayColourProvider.Orange.Colour2,
Colour = colours.Orange2
}
}
};

View File

@ -38,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapSet
Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f },
Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(),
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
Colour = OverlayColourProvider.Blue.Colour1,
Colour = colours.Blue1
}
}
};

View File

@ -11,20 +11,16 @@ namespace osu.Game.Overlays
{
private readonly OverlayColourScheme colourScheme;
public static OverlayColourProvider Red { get; } = new OverlayColourProvider(OverlayColourScheme.Red);
public static OverlayColourProvider Pink { get; } = new OverlayColourProvider(OverlayColourScheme.Pink);
public static OverlayColourProvider Orange { get; } = new OverlayColourProvider(OverlayColourScheme.Orange);
public static OverlayColourProvider Lime { get; } = new OverlayColourProvider(OverlayColourScheme.Lime);
public static OverlayColourProvider Green { get; } = new OverlayColourProvider(OverlayColourScheme.Green);
public static OverlayColourProvider Purple { get; } = new OverlayColourProvider(OverlayColourScheme.Purple);
public static OverlayColourProvider Blue { get; } = new OverlayColourProvider(OverlayColourScheme.Blue);
public static OverlayColourProvider Plum { get; } = new OverlayColourProvider(OverlayColourScheme.Plum);
public OverlayColourProvider(OverlayColourScheme colourScheme)
{
this.colourScheme = colourScheme;
}
// Note that the following five colours are also defined in `OsuColour` as `{colourScheme}{0,1,2,3,4}`.
// The difference as to which should be used where comes down to context.
// If the colour in question is supposed to always match the view in which it is displayed theme-wise, use `OverlayColourProvider`.
// If the colour usage is special and in general differs from the surrounding view in choice of hue, use the `OsuColour` constants.
public Color4 Colour0 => getColour(1, 0.8f);
public Color4 Colour1 => getColour(1, 0.7f);
public Color4 Colour2 => getColour(0.8f, 0.6f);
public Color4 Colour3 => getColour(0.6f, 0.5f);

View File

@ -80,13 +80,10 @@ namespace osu.Game.Rulesets.Mods
}
/// <summary>
/// The (legacy) score multiplier of this mod.
/// The score multiplier of this mod.
/// </summary>
/// <remarks>
/// This is not applied for newly set scores, but may be required for display purposes when showing legacy scores.
/// </remarks>
[JsonIgnore]
public virtual double ScoreMultiplier => 1;
public abstract double ScoreMultiplier { get; }
/// <summary>
/// Returns true if this mod is implemented (and playable).

View File

@ -92,6 +92,8 @@ namespace osu.Game.Rulesets.Scoring
private readonly List<HitEvent> hitEvents = new List<HitEvent>();
private HitObject lastHitObject;
private double scoreMultiplier = 1;
public ScoreProcessor()
{
accuracyPortion = DefaultAccuracyPortion;
@ -109,6 +111,15 @@ namespace osu.Game.Rulesets.Scoring
};
Mode.ValueChanged += _ => updateScore();
Mods.ValueChanged += mods =>
{
scoreMultiplier = 1;
foreach (var m in mods.NewValue)
scoreMultiplier *= m.ScoreMultiplier;
updateScore();
};
}
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
@ -224,7 +235,7 @@ namespace osu.Game.Rulesets.Scoring
case ScoringMode.Standardised:
double accuracyScore = accuracyPortion * accuracyRatio;
double comboScore = comboPortion * comboRatio;
return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics));
return (max_score * (accuracyScore + comboScore) + getBonusScore(statistics)) * scoreMultiplier;
case ScoringMode.Classic:
// This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring.

View File

@ -68,14 +68,14 @@ namespace osu.Game.Screens.OnlinePlay.Components
}
else
{
var metadataInfo = beatmap.Value.Metadata;
var metadataInfo = beatmap.Metadata;
string artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode;
string titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode;
var title = new RomanisableString($"{artistUnicode} - {titleUnicode}".Trim(), $"{metadataInfo.Artist} - {metadataInfo.Title}".Trim());
textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.Value.OnlineID.ToString(), "Open beatmap");
textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.OnlineID.ToString(), "Open beatmap");
}
}
}

View File

@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
var mods = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray();
drawableRuleset.FadeIn(transition_duration);
drawableRuleset.Child = new DifficultyIcon(item.Beatmap.Value, ruleset.RulesetInfo, mods) { Size = new Vector2(height) };
drawableRuleset.Child = new DifficultyIcon(item.Beatmap, ruleset.RulesetInfo, mods) { Size = new Vector2(height) };
}
else
drawableRuleset.FadeOut(transition_duration);

View File

@ -60,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{
Schedule(() =>
{
var beatmap = playlistItem?.Beatmap.Value;
var beatmap = playlistItem?.Beatmap;
string? lastCover = (background?.Beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover;
string? newCover = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover;

View File

@ -30,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateBeatmap()
{
sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap.Value;
sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap;
}
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };

View File

@ -17,7 +17,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
public PlaylistItemBackground(PlaylistItem? playlistItem)
{
Beatmap = playlistItem?.Beatmap.Value;
Beatmap = playlistItem?.Beatmap;
}
[BackgroundDependencyLoader]

View File

@ -112,9 +112,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
try
{
foreach (var pi in room.Playlist)
pi.MapObjects();
var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
if (existing == null)
rooms.Add(room);

View File

@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
private void updateRange(object sender, NotifyCollectionChangedEventArgs e)
{
var orderedDifficulties = Playlist.Where(p => p.Beatmap.Value != null).Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray();
var orderedDifficulties = Playlist.Select(p => p.Beatmap).OrderBy(b => b.StarRating).ToArray();
StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0);
StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0);

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -25,7 +24,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
@ -68,8 +66,8 @@ namespace osu.Game.Screens.OnlinePlay
private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both };
private readonly IBindable<bool> valid = new Bindable<bool>();
private readonly Bindable<IBeatmapInfo> beatmap = new Bindable<IBeatmapInfo>();
private IBeatmapInfo beatmap;
private IRulesetInfo ruleset;
private Mod[] requiredMods;
@ -96,10 +94,6 @@ namespace osu.Game.Screens.OnlinePlay
[Resolved]
private UserLookupCache userLookupCache { get; set; }
[CanBeNull]
[Resolved(CanBeNull = true)]
private MultiplayerClient multiplayerClient { get; set; }
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; }
@ -110,7 +104,6 @@ namespace osu.Game.Screens.OnlinePlay
{
Item = item;
beatmap.BindTo(item.Beatmap);
valid.BindTo(item.Valid);
if (item.Expired)
@ -151,7 +144,6 @@ namespace osu.Game.Screens.OnlinePlay
maskingContainer.BorderThickness = isCurrent ? 5 : 0;
}, true);
beatmap.BindValueChanged(_ => Scheduler.AddOnce(refresh));
valid.BindValueChanged(_ => Scheduler.AddOnce(refresh));
onScreenLoader.DelayedLoadStarted += _ =>
@ -166,19 +158,9 @@ namespace osu.Game.Screens.OnlinePlay
Schedule(() => ownerAvatar.User = foundUser);
}
if (Item.Beatmap.Value == null)
{
IBeatmapInfo foundBeatmap;
beatmap = await beatmapLookupCache.GetBeatmapAsync(Item.Beatmap.OnlineID).ConfigureAwait(false);
if (multiplayerClient != null)
// This call can eventually go away (and use the else case below).
// Currently required only due to the method being overridden to provide special behaviour in tests.
foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false);
else
foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false);
Schedule(() => Item.Beatmap.Value = foundBeatmap);
}
Scheduler.AddOnce(refresh);
}
catch (Exception e)
{
@ -280,32 +262,36 @@ namespace osu.Game.Screens.OnlinePlay
maskingContainer.BorderColour = colours.Red;
}
if (Item.Beatmap.Value != null)
difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) };
if (beatmap != null)
difficultyIconContainer.Child = new DifficultyIcon(beatmap, ruleset, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) };
else
difficultyIconContainer.Clear();
panelBackground.Beatmap.Value = Item.Beatmap.Value;
panelBackground.Beatmap.Value = beatmap;
beatmapText.Clear();
if (Item.Beatmap.Value != null)
if (beatmap != null)
{
beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text =>
{
text.Truncate = true;
});
beatmapText.AddLink(beatmap.GetDisplayTitleRomanisable(includeCreator: false),
LinkAction.OpenBeatmap,
beatmap.OnlineID.ToString(),
null,
text =>
{
text.Truncate = true;
});
}
authorText.Clear();
if (!string.IsNullOrEmpty(Item.Beatmap.Value?.Metadata.Author.Username))
if (!string.IsNullOrEmpty(beatmap?.Metadata.Author.Username))
{
authorText.AddText("mapped by ");
authorText.AddUserLink(Item.Beatmap.Value.Metadata.Author);
authorText.AddUserLink(beatmap.Metadata.Author);
}
bool hasExplicitContent = (Item.Beatmap.Value?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true;
bool hasExplicitContent = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true;
explicitContentPill.Alpha = hasExplicitContent ? 1 : 0;
modDisplay.Current.Value = requiredMods.ToArray();
@ -457,7 +443,7 @@ namespace osu.Game.Screens.OnlinePlay
Alpha = AllowShowingResults ? 1 : 0,
TooltipText = "View results"
},
Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item),
beatmap == null ? Empty() : new PlaylistDownloadButton(beatmap),
editButton = new PlaylistEditButton
{
Size = new Vector2(30, 30),
@ -499,7 +485,7 @@ namespace osu.Game.Screens.OnlinePlay
private sealed class PlaylistDownloadButton : BeatmapDownloadButton
{
private readonly PlaylistItem playlistItem;
private readonly IBeatmapInfo beatmap;
[Resolved]
private BeatmapManager beatmapManager { get; set; }
@ -509,10 +495,10 @@ namespace osu.Game.Screens.OnlinePlay
private const float width = 50;
public PlaylistDownloadButton(PlaylistItem playlistItem)
: base(playlistItem.Beatmap.Value.BeatmapSet)
public PlaylistDownloadButton(IBeatmapInfo beatmap)
: base(beatmap.BeatmapSet)
{
this.playlistItem = playlistItem;
this.beatmap = beatmap;
Size = new Vector2(width, 30);
Alpha = 0;
@ -532,7 +518,7 @@ namespace osu.Game.Screens.OnlinePlay
{
case DownloadState.LocallyAvailable:
// Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching.
if (beatmapManager.QueryBeatmap(b => b.MD5Hash == playlistItem.Beatmap.Value.MD5Hash) == null)
if (beatmapManager.QueryBeatmap(b => b.MD5Hash == beatmap.MD5Hash) == null)
State.Value = DownloadState.NotDownloaded;
else
{

View File

@ -2,8 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@ -12,6 +14,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@ -328,6 +331,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
[Resolved]
private OsuColour colours { get; set; }
[Resolved]
private BeatmapLookupCache beatmapLookupCache { get; set; }
private SpriteText statusText;
private LinkFlowContainer beatmapText;
@ -385,8 +391,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
SelectedItem.BindValueChanged(onSelectedItemChanged, true);
}
private CancellationTokenSource beatmapLookupCancellation;
private void onSelectedItemChanged(ValueChangedEvent<PlaylistItem> item)
{
beatmapLookupCancellation?.Cancel();
beatmapText.Clear();
if (Type.Value == MatchType.Playlists)
@ -395,17 +404,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
return;
}
if (item.NewValue?.Beatmap.Value != null)
{
statusText.Text = "Currently playing ";
beatmapText.AddLink(item.NewValue.Beatmap.Value.GetDisplayTitleRomanisable(),
LinkAction.OpenBeatmap,
item.NewValue.Beatmap.Value.OnlineID.ToString(),
creationParameters: s =>
{
s.Truncate = true;
});
}
var beatmap = item.NewValue?.Beatmap;
if (beatmap == null)
return;
var cancellationSource = beatmapLookupCancellation = new CancellationTokenSource();
beatmapLookupCache.GetBeatmapAsync(beatmap.OnlineID, cancellationSource.Token)
.ContinueWith(task => Schedule(() =>
{
if (cancellationSource.IsCancellationRequested)
return;
var retrievedBeatmap = task.GetResultSafely();
statusText.Text = "Currently playing ";
beatmapText.AddLink(retrievedBeatmap.GetDisplayTitleRomanisable(),
LinkAction.OpenBeatmap,
retrievedBeatmap.OnlineID.ToString(),
creationParameters: s => s.Truncate = true);
}), cancellationSource.Token);
}
}

View File

@ -62,7 +62,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
if (editButton != null)
host.BindValueChanged(h => editButton.Alpha = h.NewValue?.Equals(api.LocalUser.Value) == true ? 1 : 0, true);
SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap.Value, true);
SelectedItem.BindValueChanged(item => background.Beatmap.Value = item.NewValue?.Beatmap, true);
}
protected override Drawable CreateBackground() => background = new BackgroundSprite();

View File

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

View File

@ -67,8 +67,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
var multiplayerItem = new MultiplayerPlaylistItem
{
ID = itemToEdit ?? 0,
BeatmapID = item.BeatmapID,
BeatmapChecksum = item.Beatmap.Value.MD5Hash,
BeatmapID = item.Beatmap.OnlineID,
BeatmapChecksum = item.Beatmap.MD5Hash,
RulesetID = item.RulesetID,
RequiredMods = item.RequiredMods.ToArray(),
AllowedMods = item.AllowedMods.ToArray()

View File

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -398,38 +397,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private void updateCurrentItem()
{
Debug.Assert(client.Room != null);
var expectedSelectedItem = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId);
if (expectedSelectedItem == null)
return;
// There's no reason to renew the selected item if its content hasn't changed.
if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null)
return;
// Clear the selected item while the lookup is performed, so components like the ready button can enter their disabled states.
SelectedItem.Value = null;
if (expectedSelectedItem.Beatmap.Value == null)
{
Task.Run(async () =>
{
var beatmap = await client.GetAPIBeatmap(expectedSelectedItem.BeatmapID).ConfigureAwait(false);
Schedule(() =>
{
expectedSelectedItem.Beatmap.Value = beatmap;
if (Room.Playlist.SingleOrDefault(i => i.ID == client.Room?.Settings.PlaylistItemId)?.Equals(expectedSelectedItem) == true)
applyCurrentItem();
});
});
}
else
applyCurrentItem();
void applyCurrentItem() => SelectedItem.Value = expectedSelectedItem;
SelectedItem.Value = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId);
}
private void handleRoomLost() => Schedule(() =>

View File

@ -113,12 +113,8 @@ namespace osu.Game.Screens.OnlinePlay
{
itemSelected = true;
var item = new PlaylistItem
var item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
Beatmap =
{
Value = Beatmap.Value.BeatmapInfo
},
RulesetID = Ruleset.Value.OnlineID,
RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(),
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray()

View File

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

View File

@ -392,7 +392,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
foreach (var item in Playlist)
{
if (invalidBeatmapIDs.Contains(item.BeatmapID))
if (invalidBeatmapIDs.Contains(item.Beatmap.OnlineID))
item.MarkInvalid();
}
}

View File

@ -41,10 +41,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
private void createNewItem()
{
PlaylistItem item = new PlaylistItem
PlaylistItem item = new PlaylistItem(Beatmap.Value.BeatmapInfo)
{
ID = Playlist.Count == 0 ? 0 : Playlist.Max(p => p.ID) + 1,
Beatmap = { Value = Beatmap.Value.BeatmapInfo },
RulesetID = Ruleset.Value.OnlineID,
RequiredMods = Mods.Value.Select(m => new APIMod(m)).ToArray(),
AllowedMods = FreeMods.Value.Select(m => new APIMod(m)).ToArray()

View File

@ -6,11 +6,14 @@ using osu.Framework.Graphics;
using osu.Game.Screens.Play.HUD;
using osu.Game.Rulesets.Mods;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
using osu.Game.Input.Bindings;
namespace osu.Game.Screens.Select
@ -23,7 +26,10 @@ namespace osu.Game.Screens.Select
set => modDisplay.Current = value;
}
protected readonly OsuSpriteText MultiplierText;
private readonly ModDisplay modDisplay;
private Color4 lowMultiplierColour;
private Color4 highMultiplierColour;
public FooterButtonMods()
{
@ -34,6 +40,12 @@ namespace osu.Game.Screens.Select
Scale = new Vector2(0.8f),
ExpansionMode = ExpansionMode.AlwaysContracted,
});
ButtonContentContainer.Add(MultiplierText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(weight: FontWeight.Bold),
});
}
[BackgroundDependencyLoader]
@ -41,6 +53,8 @@ namespace osu.Game.Screens.Select
{
SelectedColour = colours.Yellow;
DeselectedColour = SelectedColour.Opacity(0.5f);
lowMultiplierColour = colours.Red;
highMultiplierColour = colours.Green;
Text = @"mods";
Hotkey = GlobalAction.ToggleModSelection;
}
@ -54,6 +68,17 @@ namespace osu.Game.Screens.Select
private void updateMultiplierText()
{
double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1;
MultiplierText.Text = multiplier.Equals(1.0) ? string.Empty : $"{multiplier:N2}x";
if (multiplier > 1.0)
MultiplierText.FadeColour(highMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierText.FadeColour(lowMultiplierColour, 200);
else
MultiplierText.FadeColour(Color4.White, 200);
if (Current.Value?.Count > 0)
modDisplay.FadeIn();
else

View File

@ -78,7 +78,7 @@ namespace osu.Game.Storyboards
{
get
{
string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata.BackgroundFile;
string backgroundPath = BeatmapInfo.Metadata.BackgroundFile;
if (string.IsNullOrEmpty(backgroundPath))
return false;

View File

@ -46,9 +46,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Name = { Value = "test name" },
Playlist =
{
new PlaylistItem
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
{
Beatmap = { Value = new TestBeatmap(Ruleset.Value).BeatmapInfo },
RulesetID = Ruleset.Value.OnlineID
}
}

View File

@ -7,14 +7,11 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
using osu.Game.Online.Rooms;
@ -39,9 +36,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
private readonly TestMultiplayerRoomManager roomManager;
/// <summary>
@ -407,23 +401,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId);
public override Task<APIBeatmap> GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default)
{
IBeatmapInfo? beatmap = roomManager.ServerSideRooms.SelectMany(r => r.Playlist)
.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value
?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId);
if (beatmap == null)
throw new InvalidOperationException("Beatmap not found.");
return Task.FromResult(new APIBeatmap
{
BeatmapSet = new APIBeatmapSet { OnlineID = beatmap.BeatmapSet?.OnlineID ?? -1 },
OnlineID = beatmapId,
Checksum = beatmap.MD5Hash
});
}
private async Task changeMatchType(MatchType type)
{
Debug.Assert(Room != null);

View File

@ -8,6 +8,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Database;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Screens.OnlinePlay;
@ -33,9 +34,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay
protected override Container<Drawable> Content => content;
[Resolved]
private OsuGameBase game { get; set; }
private readonly Container content;
private readonly Container drawableDependenciesContainer;
private DelegatedDependencyContainer dependencies;
@ -62,19 +60,16 @@ namespace osu.Game.Tests.Visual.OnlinePlay
drawableDependenciesContainer.Clear();
dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies();
drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents);
var handler = OnlinePlayDependencies.RequestsHandler;
// Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead.
// To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead.
var beatmapManager = dependencies.Get<BeatmapManager>();
((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, beatmapManager);
});
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("setup API", () =>
{
var handler = OnlinePlayDependencies.RequestsHandler;
((DummyAPIAccess)API).HandleRequest = request => handler.HandleRequest(request, API.LocalUser.Value, game);
});
}
/// <summary>
/// Creates the room dependencies. Called every <see cref="Setup"/>.
/// </summary>

View File

@ -43,16 +43,9 @@ namespace osu.Game.Tests.Visual.OnlinePlay
if (ruleset != null)
{
room.Playlist.Add(new PlaylistItem
room.Playlist.Add(new PlaylistItem(new BeatmapInfo { Metadata = new BeatmapMetadata() })
{
RulesetID = ruleset.OnlineID,
Beatmap =
{
Value = new BeatmapInfo
{
Metadata = new BeatmapMetadata()
}
}
});
}

View File

@ -4,12 +4,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
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;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.OnlinePlay
{
@ -33,9 +37,9 @@ namespace osu.Game.Tests.Visual.OnlinePlay
/// </summary>
/// <param name="request">The API request to handle.</param>
/// <param name="localUser">The local user to store in responses where required.</param>
/// <param name="game">The game base for cases where actual online requests need to be sent.</param>
/// <param name="beatmapManager">The beatmap manager to attempt to retrieve beatmaps from, prior to returning dummy beatmaps.</param>
/// <returns>Whether the request was successfully handled.</returns>
public bool HandleRequest(APIRequest request, APIUser localUser, OsuGameBase game)
public bool HandleRequest(APIRequest request, APIUser localUser, BeatmapManager beatmapManager)
{
switch (request)
{
@ -128,6 +132,26 @@ namespace osu.Game.Tests.Visual.OnlinePlay
Statistics = new Dictionary<HitResult, int>()
});
return true;
case GetBeatmapsRequest getBeatmapsRequest:
var result = new List<APIBeatmap>();
foreach (int id in getBeatmapsRequest.BeatmapIds)
{
var baseBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id);
if (baseBeatmap == null)
{
baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo;
baseBeatmap.OnlineID = id;
baseBeatmap.BeatmapSet!.OnlineID = id;
}
result.Add(OsuTestScene.CreateAPIBeatmap(baseBeatmap));
}
getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = result });
return true;
}
return false;

View File

@ -225,12 +225,24 @@ namespace osu.Game.Tests.Visual
protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset);
/// <summary>
/// Returns a sample API Beatmap with BeatmapSet populated.
/// Returns a sample API beatmap with a populated beatmap set.
/// </summary>
/// <param name="ruleset">The ruleset to create the sample model using. osu! ruleset will be used if not specified.</param>
protected APIBeatmap CreateAPIBeatmap(RulesetInfo ruleset = null)
protected APIBeatmap CreateAPIBeatmap(RulesetInfo ruleset = null) => CreateAPIBeatmap(CreateBeatmap(ruleset ?? Ruleset.Value).BeatmapInfo);
/// <summary>
/// Constructs a sample API beatmap set containing a beatmap.
/// </summary>
/// <param name="ruleset">The ruleset to create the sample model using. osu! ruleset will be used if not specified.</param>
protected APIBeatmapSet CreateAPIBeatmapSet(RulesetInfo ruleset = null) => CreateAPIBeatmapSet(CreateBeatmap(ruleset ?? Ruleset.Value).BeatmapInfo);
/// <summary>
/// Constructs a sample API beatmap with a populated beatmap set from a given source beatmap.
/// </summary>
/// <param name="original">The source beatmap.</param>
public static APIBeatmap CreateAPIBeatmap(IBeatmapInfo original)
{
var beatmapSet = CreateAPIBeatmapSet(ruleset ?? Ruleset.Value);
var beatmapSet = CreateAPIBeatmapSet(original);
// Avoid circular reference.
var beatmap = beatmapSet.Beatmaps.First();
@ -243,18 +255,16 @@ namespace osu.Game.Tests.Visual
}
/// <summary>
/// Returns a sample API BeatmapSet with beatmaps populated.
/// Constructs a sample API beatmap set containing a beatmap from a given source beatmap.
/// </summary>
/// <param name="ruleset">The ruleset to create the sample model using. osu! ruleset will be used if not specified.</param>
protected APIBeatmapSet CreateAPIBeatmapSet(RulesetInfo ruleset = null)
/// <param name="original">The source beatmap.</param>
public static APIBeatmapSet CreateAPIBeatmapSet(IBeatmapInfo original)
{
var beatmap = CreateBeatmap(ruleset ?? Ruleset.Value).BeatmapInfo;
Debug.Assert(beatmap.BeatmapSet != null);
Debug.Assert(original.BeatmapSet != null);
return new APIBeatmapSet
{
OnlineID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID,
OnlineID = original.BeatmapSet.OnlineID,
Status = BeatmapOnlineStatus.Ranked,
Covers = new BeatmapSetOnlineCovers
{
@ -262,29 +272,29 @@ namespace osu.Game.Tests.Visual
Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg",
List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg"
},
Title = beatmap.Metadata.Title,
TitleUnicode = beatmap.Metadata.TitleUnicode,
Artist = beatmap.Metadata.Artist,
ArtistUnicode = beatmap.Metadata.ArtistUnicode,
Title = original.Metadata.Title,
TitleUnicode = original.Metadata.TitleUnicode,
Artist = original.Metadata.Artist,
ArtistUnicode = original.Metadata.ArtistUnicode,
Author = new APIUser
{
Username = beatmap.Metadata.Author.Username,
Id = beatmap.Metadata.Author.OnlineID
Username = original.Metadata.Author.Username,
Id = original.Metadata.Author.OnlineID
},
Source = beatmap.Metadata.Source,
Tags = beatmap.Metadata.Tags,
Source = original.Metadata.Source,
Tags = original.Metadata.Tags,
Beatmaps = new[]
{
new APIBeatmap
{
OnlineID = ((IBeatmapInfo)beatmap).OnlineID,
OnlineBeatmapSetID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID,
Status = beatmap.Status,
Checksum = beatmap.MD5Hash,
AuthorID = beatmap.Metadata.Author.OnlineID,
RulesetID = beatmap.Ruleset.OnlineID,
StarRating = beatmap.StarRating,
DifficultyName = beatmap.DifficultyName,
OnlineID = original.OnlineID,
OnlineBeatmapSetID = original.BeatmapSet.OnlineID,
Status = ((BeatmapInfo)original).Status,
Checksum = original.MD5Hash,
AuthorID = original.Metadata.Author.OnlineID,
RulesetID = original.Ruleset.OnlineID,
StarRating = original.StarRating,
DifficultyName = original.DifficultyName,
}
}
};

View File

@ -0,0 +1,61 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace osu.Game.Utils
{
public static class NamingUtils
{
/// <summary>
/// Given a set of <paramref name="existingNames"/> and a target <paramref name="desiredName"/>,
/// finds a "best" name closest to <paramref name="desiredName"/> that is not in <paramref name="existingNames"/>.
/// </summary>
/// <remarks>
/// <para>
/// This helper is most useful in scenarios when creating new objects in a set
/// (such as adding new difficulties to a beatmap set, or creating a clone of an existing object that needs a unique name).
/// If <paramref name="desiredName"/> is already present in <paramref name="existingNames"/>,
/// this method will append the lowest possible number in brackets that doesn't conflict with <paramref name="existingNames"/>
/// to <paramref name="desiredName"/> and return that.
/// See <c>osu.Game.Tests.Utils.NamingUtilsTest</c> for concrete examples of behaviour.
/// </para>
/// <para>
/// <paramref name="desiredName"/> and <paramref name="existingNames"/> are compared in a case-insensitive manner,
/// so this method is safe to use for naming files in a platform-invariant manner.
/// </para>
/// </remarks>
public static string GetNextBestName(IEnumerable<string> existingNames, string desiredName)
{
string pattern = $@"^(?i){Regex.Escape(desiredName)}(?-i)( \((?<copyNumber>[1-9][0-9]*)\))?$";
var regex = new Regex(pattern, RegexOptions.Compiled);
var takenNumbers = new HashSet<int>();
foreach (string name in existingNames)
{
var match = regex.Match(name);
if (!match.Success)
continue;
string copyNumberString = match.Groups[@"copyNumber"].Value;
if (string.IsNullOrEmpty(copyNumberString))
{
takenNumbers.Add(0);
continue;
}
takenNumbers.Add(int.Parse(copyNumberString));
}
int bestNumber = 0;
while (takenNumbers.Contains(bestNumber))
bestNumber += 1;
return bestNumber == 0
? desiredName
: $"{desiredName} ({bestNumber})";
}
}
}

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.9.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.214.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.217.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
<PackageReference Include="Sentry" Version="3.14.0" />
<PackageReference Include="SharpCompress" Version="0.30.1" />

View File

@ -60,7 +60,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.214.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.217.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.211.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
@ -83,7 +83,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.214.0" />
<PackageReference Include="ppy.osu.Framework" Version="2022.217.0" />
<PackageReference Include="SharpCompress" Version="0.30.1" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />